// ==UserScript== // @name IMDB UTE: IMDB Ultimate Thumbnail Expander // @description Expands thumbnail images in IMDB into larger floating preview images on mouse hover. // @namespace http://myxp.anandkumar.me // @include http://www.imdb.com/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_log // @grant GM_addStyle // @version 1.2.5 // @downloadURL https://update.greasyfork.icu/scripts/8844/IMDB%20UTE%3A%20IMDB%20Ultimate%20Thumbnail%20Expander.user.js // @updateURL https://update.greasyfork.icu/scripts/8844/IMDB%20UTE%3A%20IMDB%20Ultimate%20Thumbnail%20Expander.meta.js // ==/UserScript== /** Change Log: Date Version Notes 20131011 1.2.5 *Reposition loaded image on a timer. 20130827 1.2.4 *Revert 1.2.1 since script was corrected by author. 20130804 1.2.3 *Updated rules for video thumbnail images. 20130426 1.2.2 *Changed require URLs to https. 20130108 1.2.1 *Moved the "gm_config extras" script with corrections to anandkumar.me domain. 20120829 1.2.0 *Added @grant directives to comply with Greasemonkey 1.0. *Removed dependence on custom updater since Greasemonkey now has an internal updater. *Copied the floating image repositioning logic from my Flickr script. *Moved all floating image related code to a single class. This is the first step towards unifying my other scripts that use the same logic. 20120606 1.1.0 *Fine-tuned the image positioning algorithm. *Added "Ctrl" key override to persist preview image. 20120604 1.0.2 *Added support for a delay timer and a corresponding configuration item under settings. *Added support for the setting the preview image to "Snap to Cursor". 20111112 1.0.1 *Added missing escaping to some reg-expressions. 20111103 1.0.0 *Official release of script. */ var DEBUG = false; var newWidth = "200"; var newHeight = "200"; var GMImg_position = "absolute"; var GMImg_marginleft = "0"; var GMImg_margintop = "0"; var GMImg_left = ""; var GMImg_top = ""; var GMImg_right = ""; var GMImg_bottom = ""; var prevTarget = null; var floatimg; var timerRunning = false, timerID = null; var global_mouse_x=0, global_mouse_y=0; var persistPreview = false; //From Config// var settings_max_width, settings_max_height, settings_auto_size, settings_snap_to_cursor; var settings_expand_actorimgs,settings_expand_titleimgs,settings_expand_previewimgs; var settings_x_factor,settings_y_factor; var settings_timer_ms; var xhrCnt = 0; var debug_msg_div = "flute_debug_msg_div"; var fio; /** **Config screen */ var lang = GM_config.gets('lang','en'); // get the language - or set it to 'en' if it was not yet stored GM_config.init('Configuration for IMDB UTE',{ timer_ms : { label: 'Preview Delay (milliseconds)', type: 'int', default: 0, min: 0, max: 10000 }, max_width: { label: 'Max Display Width:',section:['Preview Image properties'], type: 'int', default: 640, min: 50, max: 5000 }, max_height:{ label: 'Max Display Height:', type: 'int', default: 480, min: 50, max: 5000 }, auto_size: { label: 'Enable Auto size:', type: 'checkbox', default: true }, x_factor : { label: 'Auto-size Screen Width % :', type: 'int', default: 60, min: 1, max: 100 }, y_factor : { label: 'Auto-size Screen Height %:', type: 'int', default: 60, min: 1, max: 100 }, snap_to_cursor : { label: 'Snap to Cursor', type: 'checkbox', default: false } /*expand_actorimgs : { label: 'Actor Images', section:['Control which images get expanded'], type: 'checkbox', default: true }, expand_titleimgs : { label: 'Title Images', type: 'checkbox', default: true }, expand_previewimgs: { label: 'Preview Images', type: 'checkbox', default: true }*/ }, GM_config.eCSS, { open: function() { var idx=0; GM_config.addBorder(); // add a fancy border GM_config.resizeFrame('480px','360px'); // resize the config window GM_config.addTooltip(idx++,'Set the delay (in milliseconds) to wait before displaying the preview image.'); GM_config.addTooltip(idx++,'The maximum Width, in pixels, of the displayed image. Use this if you want to retreive large images but want to constrain the displayed image size.'); GM_config.addTooltip(idx++,'The maximum Height, in pixels, of the displayed image. Use this if you want to retreive large images but want to constrain the displayed image size.'); GM_config.addTooltip(idx++,'When enabled, the maximum width and height of the displayed image is automatically constrained to two-thirds of the browser window size. Selecting this option will ignore the custom maximum size settings above.'); GM_config.addTooltip(idx++,'If Auto-Size is enabled, this option specifies the screen Width % to use.'); GM_config.addTooltip(idx++,'If Auto-Size is enabled, this option specifies the screen Height % to use.'); GM_config.addTooltip(idx++,'Enable this option to make the preview image follow the mouse cursor.'); // GM_config.sections2tabs(); // convert the sections to tabs GM_config.fadeOut(); //Fadeout the rest of the screen. }, save: function() { //Update the settings and close the dialog GetUserSettings(); //GM_config.fadeIn(); GM_config.close(); }, close: function() { GM_config.fadeIn(); //GM_config.close(); } } ); /** **Main code starts here */ (function(){ try{ if (typeof unsafeWindow != 'object') { window.unsafeWindow = window; } window.addEventListener('load' ,function(event){Init ();},true); window.addEventListener('mousemove',function(event){DoMouseOver(event);},true); window.addEventListener('keydown' ,function(event){DoKeyDown (event);},true); window.addEventListener('keyup' ,function(event){DoKeyUp (event);},true); } catch(e){ GM_log(e.message); } GM_registerMenuCommand('imute: Configuration',GM_config.open); })(); /** * Get the user-settings for this script. */ function GetUserSettings(){ //Correct any incorrect settings //Read the settings into local variables. settings_max_width = GM_config.get('max_width'); settings_max_height = GM_config.get('max_height'); settings_auto_size = GM_config.get('auto_size'); settings_x_factor = GM_config.get('x_factor'); settings_y_factor = GM_config.get('y_factor'); settings_snap_to_cursor = GM_config.get('snap_to_cursor'); settings_timer_ms = GM_config.get('timer_ms'); //settings_expand_actorimgs = GM_config.get('expand_actorimgs'); //settings_expand_titleimgs = GM_config.get('expand_titleimgs'); //settings_expand_previewimgs = GM_config.get('expand_previewimgs'); //Perform any validation and re-calculation of the retreived settings here. settings_x_factor = settings_x_factor/100; settings_y_factor = settings_y_factor/100; } /** * Handle all the initializations here. */ function Init(){ var isInIFrame = (window.location != window.parent.location) ? true : false; if(isInIFrame) return; //Get the user's settings GetUserSettings(); //Create and attach the floating image. var newlink; var settingsimg fio = new FloatImg(); //Create the settings icon settingsimg = document.createElement('IMG'); settingsimg.className="shadow"; settingsimg.id="imute_settings_icon"; settingsimg.alt="imute settings"; settingsimg.src="data:image/png;base64,"+ "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"; settingsimg.width = "32"; settingsimg.height = "32"; settingsimg.loaded = "false"; settingsimg.addEventListener('click', function(){GM_config.open();}, true); document.body.appendChild(settingsimg); var debugmsg = document.createElement('div'); debugmsg.id = debug_msg_div; document.body.appendChild(debugmsg); //Set defaults. GMImg_left = "1"; GMImg_top = "1"; GMImg_position = "fixed"; AddScriptCSS(); RepositionTimer(); } /** * Handles the global mouseover event. * The main entry function from where everything is kicked-off. */ function DoMouseOver(e){ //If the floating image is not yet initialized, exit. if(fio==null) return; //Get the event variable. if (!e) var e = window.event; var tg = (window.event) ? e.srcElement : e.target; //Extract the mouse co-ordinates GetCoordinate(e); //If the mouse is over the floatimg, either accidentally or deliberately, ignore it. if(tg.id == fio.getId()) return; //If the preview image must be repositioned upon mouse move, do it here: if(settings_snap_to_cursor){ if(persistPreview==false){ fio.reposition(); } else{ return; } } //If this mouseover event is for the same element as before, exit. //We are only interested when the mouse enters/leaves an element. if(prevTarget !=null && tg == prevTarget) { return; } //Store the current target to compare against in the next mouse over event. prevTarget = tg; //If the mouse is over any "other" element, hide the floating image. ImgDoMouseOut(); //If the target does not have a valid image, url, exit. var img_src = GetDisplayImageURL(tg); if(img_src == "") return; //If the current image does not meet criteria, exit. if(!canBeExpanded(img_src) || img_src.match(/\/nopicture|nophoto\//)){ return; } /**END of validations**/ //Once all the validations are passed, kick-start the image preview process. fio.srcElem = tg; StartImagePreviewTimer(tg); } /** * Starts the preview image display timer. */ function StartImagePreviewTimer(tg){ //First, stop the global timer, if it's running. if(timerRunning){ StopImagePreviewTimer(); } timerRunning = true; timerID = setTimeout(function(){ShowImagePreview(tg);}, settings_timer_ms); timerID = setTimeout(function(){ShowImagePreview(tg);}, settings_timer_ms); } /** * Stops the active global timer. */ function StopImagePreviewTimer(){ timerRunning = false; if(timerID!=null) clearTimeout(timerID); } /** * Before the new image loads, the image size is not known. This results in the image not positioned correctly until the user moves the mouse. * So, keep repositioning the image on a timer. */ function RepositionTimer(){ if(fio != null && fio.isLoaded()){ fio.reposition(); } setTimeout(function(){RepositionTimer();}, 100); } /** * The main function to call to begin the process of popping-up the preview image. * Accepts an event object's target (usually, an image node). */ function ShowImagePreview(tg){ StopImagePreviewTimer(); //Invalidate current and older XHRs. xhrCnt++; //Get the expanded image url. var zoomedUrl = GetPreviewURL(tg, 1000, settings_max_width, settings_max_height); //message("zoomedUrl="+zoomedUrl,false,true); if(zoomedUrl != ""){ //Assign the unchecked image URL so that the image displays immediately. fio.setUrl(zoomedUrl); if(persistPreview==false){ //Do not reposition if the user is currently pressing the ctrl-key down. fio.reposition(); } } else{ fio.clear(); return; } //If the image is surrounded by an anchor tag, copy its href to the floatimg's parent anchor tag. //This helps if the user wants to click the image but the floating image gets "in the way". if(tg.parentNode && tg.parentNode.href){ fio.setLinkUrl(tg.parentNode.href); } else if(tg.href){ fio.setLinkUrl(tg.href); } //Finally, show the image. fio.show(); } /** * Hide the floating image once the mouse cursor leaves an element. * This function is not event-driven by the browser but is called when * "DoMouseOver()" detects a change of target. */ function ImgDoMouseOut(){ if(persistPreview==false){ //Stop any timers: StopImagePreviewTimer(); //If the floating image is not yet created, exit. if(fio==null) return; fio.clear(); } } /** * Event handler that captures control key presses. */ function DoKeyDown(e){ if (e.ctrlKey){ persistPreview = true; } } /** * Event handler that cleans-up actions done during the keydown actions. */ function DoKeyUp(e){ if (e.keyCode == 17){ persistPreview = false; } } /** * Add CSS for our custom elements. */ function AddScriptCSS(){ //Common style var GM_style = "img.GM_thumbnail { display:none; "+ "height:auto !important;"+ "width:auto !important; "+ "position: " + GMImg_position + " ; " + "margin-left:" + GMImg_marginleft + "px ; " + "margin-top:" + GMImg_margintop + "px ; " + "z-index:9999;"+ "background-color:transparent !important;"+ (GMImg_left ==""?"":"left:" +GMImg_left + "px ; ") + (GMImg_top ==""?"":"top:" +GMImg_top + "px ; ") + (GMImg_bottom==""?"":"bottom:"+GMImg_bottom+ "px ; ") + (GMImg_right ==""?"":"right:" +GMImg_right + "px ; ") + "} "; GM_style += ".shadow {"+ "-moz-box-shadow: 3px 3px 4px #000;"+ "-webkit-box-shadow: 3px 3px 4px #000;"+ "box-shadow: 3px 3px 4px #000;"+ "}"; GM_style += "img#imute_settings_icon{ "+ "position:fixed;"+ "right:0px;"+ "bottom:0px;"+ "opacity:0.75"+ "}"; GM_style += "#"+debug_msg_div+"{ "+ "position:fixed;"+ "left:0px;"+ "top:0px;"+ "background-color:white;"+ "width:100%;"+ "z-index:99999;"+ "}"; GM_addStyle(GM_style); } /** * Checks if the image can be expanded or not. */ function canBeExpanded(img_url){ var bRet = false; if(img_url.match(/\_CR(\d+),(\d+),(\d+),(\d+)/)){ //For imdb hosted images. bRet = true; } else if(img_url.match(/\_SX\d+/) || img_url.match(/\_SY\d+/)){ bRet = true; } else if(img_url.match(/\_AA\d+\_/)){ //For amazon hosted images for imdb. bRet = true; } else if (img_url.match(/\_SS\d+/)){ bRet = true; } else if(img_url.match(/fbcdn-profile(.*)_q.jpg$/)){ //Expand facebook profile images bRet = true } //message(img_url+".match(/fbcdn-profile(.*)_q.jpg$/) = "+img_url.match(/fbcdn-profile(.*)_q.jpg$/),true,true); return bRet; } /** * Given a target element, this function returns the image url associated with the element. * Note: The image url returned is not validated. */ function GetDisplayImageURL(elem){ var url = ""; if (elem.nodeName == 'IMG'){ url = elem.src; } else if (elem.nodeName == 'LI' || elem.nodeName == 'DIV'){ if(elem.style!= null && elem.style.backgroundImage!= null) { try{ url = elem.style.backgroundImage.match(/\"(.*)\"/)[1]; }catch(e){ d(e); return ""; } } } return url; } /** * Custom zooming function for imdb after MANY trial and error! **/ function GetPreviewURL(tg,newSize,imgWidth,imgHeight){ var zoomedUrl; var url = GetDisplayImageURL(tg); //message("url="+url,true,true); if(url == "") return; //Try to expand facebook profile pictures. if(url.match(/fbcdn-profile(.*)_q.jpg$/)){ zoomedUrl = url.replace(/_q.jpg$/,"_b.jpg"); message("zoomedurl="+zoomedUrl,true,true); return zoomedUrl; } var urlSX = url.match(/\_SX\d+/)==null?"":url.match(/\_SX(\d+)/)[1]; var urlSY = url.match(/\_SY\d+/)==null?"":url.match(/\_SY(\d+)/)[1]; var urlSS = url.match(/\_SS\d+/)==null?"":url.match(/\_SS(\d+)/)[1];; //var imgType = url.split('.').pop(); var urlC1 = "", urlC2 = "", urlC3 = "", urlC4 = ""; var urlRatio, imgRatio; var newX, newY, newWidth, newHeight; var cropArr = url.match(/\_CR(\d+),(\d+),(\d+),(\d+)/); if (cropArr != null){ urlC1 = cropArr[1]; urlC2 = cropArr[2]; urlC3 = cropArr[3]; urlC4 = cropArr[4]; newX = urlC1; newY = urlC2; /*urlRatio = 1.00; newWidth = urlC3; newHeight = urlC4; if (urlC1 != ""){ urlRatio = newWidth/newHeight; }*/ } else if(urlSX !="" || urlSY != ""){ //No CR found. Using SX, SY /*newWidth = parseInt(urlSX==""?newSize:urlSX); newHeight = parseInt(urlSY==""?newSize:urlSY); urlRatio = newWidth / newHeight;*/ } imgWidth = parseInt(imgWidth); imgHeight = parseInt(imgHeight); imgRatio = imgWidth / imgHeight; //Actual Zooming takes place here if (imgRatio < 1.0){ newHeight = newSize; newWidth = Math.round(newSize * imgRatio); } else if (imgRatio > 1.0){ newWidth = newSize; newHeight = Math.round(newSize / imgRatio); } else{ newWidth = newSize; newHeight = newSize; } if(urlC1 != "" && urlC1 != "0") { newX = parseInt(urlC1) + parseInt(newWidth); } if(urlC2 != "" && urlC2 != "0") { newY = 0 + parseInt(urlC2) + parseInt(newHeight); } /* GM_log("urlC1 = "+urlC1+", urlC2 = "+urlC2+", urlC3 = "+urlC3+", urlC4 = "+urlC4); GM_log( "urlRatio="+urlRatio+" , newWidth="+newWidth+" newHeight="+newHeight ); */ zoomedUrl = url; /*If we have both the CR and SS, just change the SS to preserve correct cropping.*/ if(zoomedUrl.match(/\_CR\d+/) != null && zoomedUrl.match(/\_SS\d+_/) != null){ zoomedUrl = zoomedUrl.replace(/\_SS\d+\_/, "_SS"+newSize+"_"); } else{ //zoomedUrl = zoomedUrl.replace(/_CR\d+,\d+,\d+,\d+_/, "_CR" + newX + "," + newY + "," + newWidth + "," + newHeight + "_"); zoomedUrl = zoomedUrl.replace(/\_CR\d+,\d+,\d+,\d+\_/g, "_CR" + newX + "," + newY + ",0,0" + "_"); zoomedUrl = zoomedUrl.replace(/\_SS\d+\_/, ""); } /*Always replace SX and SY, if present.*/ zoomedUrl = zoomedUrl.replace(/\_SY\d+\_/, "_SY"+newHeight+"_"); zoomedUrl = zoomedUrl.replace(/\_SX\d+\_/, "_SX"+newWidth+"_"); /*Prevent too large an image from being used by Squaring it.*/ if(zoomedUrl.match(/\_CR\d+/) != null){ if(urlC3 < newWidth || urlC4 < newHeight){ zoomedUrl = zoomedUrl.replace(/\_SS\d+\_/, ""); } else{ zoomedUrl = zoomedUrl.replace(/\_SS\d+\_/, "_SS"+newSize+"_"); } } if(url.match(/\_AA\d+\_/)){ //For amazon hosted images for imdb. zoomedUrl = zoomedUrl.replace(/\_AA(\d+)\_/, "_"); } /*Erase any Border Overlays in the URL to get a clean image*/ zoomedUrl = zoomedUrl.replace(/\_BO.+\_/, ""); zoomedUrl = zoomedUrl.replace(/_PIimdb\-blackband\-.+\_/,""); /*Zero-out the SP param (needs more research)*/ zoomedUrl = zoomedUrl.replace(/_SP\d+,\d+/,"_SP0,0"); return zoomedUrl; } /** * Helper function that parses and stores the mouse positon from an event object. */ function GetCoordinate(e){ global_mouse_x = e.clientX + document.body.scrollLeft; global_mouse_y = e.clientY + document.body.scrollTop; return true } /** * Custom debug output function for writing to Error Console. */ function d(msg,force){ if(force==null) force=false; if(DEBUG==true || force==true){ GM_log(msg); } } /** * Custom debug output function that displays log output on the webpage. */ function message(msg,clearPrevious,force){ if(force==null) force=false; if(clearPrevious==null) clearPrevious=true; if(DEBUG == true || force == true){ var msgdiv = document.getElementById(debug_msg_div); if(clearPrevious){ msgdiv.innerHTML = msg; } else{ msgdiv.innerHTML += "
"+msg; } } } /** * Common class representing the floating preview image container. */ function FloatImg(img){ var element; //The image element represented by this class; var link_element; //The (optional) anchor element. var link_url; //The url to navigate to when the image is clicked. var srcElem; //From which element was the image url captured. var loaded; var id; var width, height; //Image dimensions. /*var url; //The image url. var top, bottom, left, right; //Image style co-ordinates. var maxWidth, maxHeight; //Image style dimentions.*/ var thisObj = this; this.getImageElement = GetImageElement; this.setLinkElement = SetLinkElement; this.getId = GetId; this.setUrl = LoadImg; this.clear = Clear; this.show = Show; this.reposition = RepositionPreviewImage; this.setSrc = SetSrc; this.setLinkUrl = SetLinkUrl; this.setTop = SetTop; this.getTop = GetTop; this.setBottom = SetBottom; this.getBottom = GetBottom; this.setLeft = SetLeft; this.getLeft = GetLeft; this.setRight = SetRight; this.getRight = GetRight; this.setWidth = SetWidth; this.getWidth = GetWidth; this.setHeight = SetHeight; this.getHeight = GetHeight; this.setMaxWidth = SetMaxWidth; this.getMaxWidth = GetMaxWidth; this.setMaxHeight= SetMaxHeight; this.getMaxHeight= GetMaxHeight; this.isLoaded = GetLoaded; this.setLoaded = SetLoaded; //Perform object initialization. //"img" is the floating image element. if(img){ try{ this.element = img; this.id = img.id; this.Clear(); } catch(e){ d(e.message); } } else{ //Try to create a floating image. this.element = document.createElement('IMG'); this.element.className = 'GM_thumbnail shadow'; this.element.id = "floatimg"; this.element.src = ""; this.element.alt = "Loading..."; //Now create the surrounding link element. this.link_element = document.createElement('A'); this.link_element.id="floatimglink"; this.link_element.href=""; this.link_element.appendChild(this.element); document.body.appendChild(this.link_element); } /** * Function to initiate loading of an image into the floating image container. * @param callback A function to call once the image has been loaded into cache. If given, then the image is displayed only once the image has been downloaded. */ function LoadImg(url,callback) { if(callback){ var img = new Image(); var that = this; img.onload = function(){ /*message("New height="+this.height,true,false); message("New width="+this.width,true,true);*/ that.height = this.height; that.width = this.width; if(callback){ that.setSrc(url); that.setLoaded(true); callback(); } return true; }; img.onerror = function(){ d('Image '+url+' could not be loaded...'); }; img.src = url; this.setLoaded(false); } else{ this.clear(true); this.element.src = url; this.setLoaded(true); } } /** * Clears the image url and link, and hides the image element. */ function Clear(onlyImg){ //Clear the image. this.setLoaded(false); this.element.src = ""; this.element.style.display='none'; if(onlyImg==null || onlyImg == false){ //Clear the link. this.link_url = ""; } } function Show(){ this.element.style.display = 'block'; } /** * This function resizes and positions the floating preview image node. * The function assumes that the image is already loaded or is already loading. * TODO::Declare local variables for the "settings_*" variables inside the class. */ function RepositionPreviewImage(){ var pageWidth = PageWidth(); var pageHeight = PageHeight(); var maxWidth, maxHeight; var mouseX, mouseY; var cursorPadding; mouseX = global_mouse_x; mouseY = global_mouse_y; cursorPadding = 20; //Now to compute the display size of the floating image. if(settings_auto_size){ maxWidth = Math.floor(Math.max(100,pageWidth * settings_x_factor)); maxHeight = Math.floor(Math.max(100,pageHeight * settings_y_factor)); } else{ maxWidth = Math.floor(Math.max(settings_max_width ,1)); maxHeight = Math.floor(Math.max(settings_max_height,1)); } //We only set the maximum size for the image. This way, the image will keep its aspect ratio. this.setMaxWidth (maxWidth); this.setMaxHeight(maxHeight); //Reset the IMG position based on mouse position. if(settings_snap_to_cursor){ var imgWidth=0, imgHeight=0, availableWidth=0, availableHeight=0; var displaySide = {horizontal: "right", vertical: "bottom"}; var newTop = 0; //Find out the maximum available size in the x and y axes. availableWidth = Math.max(pageWidth - (int(mouseX) + int(cursorPadding) ), (mouseX - cursorPadding) ); availableHeight = Math.max(pageHeight - (int(mouseY) + int(cursorPadding) ), (mouseY - cursorPadding) ); //Resize the image container if the available area is not enough. this.setMaxWidth( Math.min(availableWidth,maxWidth) ); //fio.setMaxHeight( Math.min(availableHeight,maxHeight) ); //Do this after manipulating the image's max size. Width and Height are only available after. imgWidth = this.getWidth(); imgHeight = this.getHeight(); //Choose which side of the cursor the image should be displayed. if( (int(imgWidth)+int(cursorPadding)) > (pageWidth-mouseX) ){ displaySide.horizontal = "left"; } /*if( (int(imgHeight)+int(cursorPadding)) > (pageHeight-mouseY) ){ displaySide.vertical = "top"; }*/ //Now to position the image. //First, decide on the horizontal position for the image. if(displaySide.horizontal == "left"){ this.setRight( int(mouseX) - int(cursorPadding) ); this.setLeft( int(mouseX) - int(cursorPadding) - int(imgWidth) ); } else if(displaySide.horizontal == "right"){ this.setLeft( int(mouseX) +int(cursorPadding) ); this.setRight( int(mouseX) + int(cursorPadding) + int(imgWidth) ); } //Next, decide on the vertical position for the image. /* *Here's my logic with this one (after MANY trial-and-error!): * By default, we want the image to appear below the mouse cursor. So, we set the preview's top to the mouseY, plus some padding. * Next, we check if, given this new top position, the image will go further than the page's height. * If so, we pull up the image's top by the amount that it exceeds the page's height. * This will make the preview image's bottom exactly align with the page's bottom automatically. * TODO::Check if, after correcting the image's bottom, the image's top goes above the screen. If so, shrink the image by the overflowing amount. */ newTop = int(mouseY) + int(cursorPadding); if(newTop + imgHeight > pageHeight){ newTop = newTop - (newTop+imgHeight - pageHeight); } this.setTop( int(newTop) ); } else{ //If the image is set not to follow the cursor, choose which corner of the screen to position the image in. //First, decide on the horizontal corner for the image. if(mouseX < pageWidth/2){ this.setLeft(); //Set to "auto" this.setRight(1); } else{ this.setRight(); //Set to "auto" this.setLeft(1); } //Next, decide on the vertical corner for the image. if(mouseY < pageHeight/2){ this.setTop(); //Set to "auto" this.setBottom(1); } else{ this.setTop(1); this.setBottom(); //Set to "auto" } } } /** * Returns the page width in pixels */ function PageWidth() { return window.innerWidth != null? window.innerWidth: document.body != null? document.body.clientWidth:null; } /** * Returns the page height in pixels */ function PageHeight() { return window.innerHeight != null? window.innerHeight: document.body != null? document.body.clientHeight:null; } /** * Returns the integer representation of a string. */ function int(str){ return parseInt(str); } function SetSrc(src){ this.element.src = src; } function GetId(){ return this.id; } function SetLinkElement(elem){ this.link_element = elem; } function SetLinkUrl(url){ this.link_element.href = url; } function GetLinkUrl(){ return this.link_element.href; } function GetImageElement(){ return this.element; } function GetHeight(){ return this.element.height; } function SetHeight(height){ this.element.height = height; } function GetWidth(){ return this.element.width; } function SetWidth(width){ this.element.width = width; } function GetMaxWidth(){ return this.element.style.maxWidth; } function SetMaxHeight(height){ this.element.style.maxHeight = height+"px"; } function GetMaxHeight(){ return this.element.style.maxHeight; } function SetMaxWidth(width){ this.element.style.maxWidth = width+"px"; } function GetMaxWidth(){ return this.element.style.maxWidth; } function GetTop(){ return this.element.style.top; } function SetTop(top){ if(top){ this.element.style.top = top+"px"; } else{ this.element.style.top = "auto"; } } function GetBottom(){ return this.element.style.bottom; } function SetBottom(bottom){ if(bottom){ this.element.style.bottom = bottom+"px"; } else{ this.element.style.bottom = "auto"; } } function GetLeft(){ return this.element.style.left; } function SetLeft(left){ if(left){ this.element.style.left = left+"px"; } else{ this.element.style.left = "auto"; } } function GetRight(){ return this.element.style.right; } function SetRight(right){ if(right){ this.element.style.right = right+"px"; } else{ this.element.style.right = "auto"; } } function GetLoaded(){ return this.loaded; } function SetLoaded(loaded){ this.loaded = loaded; } }