Last active
January 8, 2020 12:54
-
-
Save espretto/34b40b2ccaa0f4f5ae9a79f0e98b79bb to your computer and use it in GitHub Desktop.
js: magnifying glass for images
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* magnifying glass for images | |
* @licence MIT | |
*/ | |
(function () { | |
/** avoid subpixel rendering */ | |
function px (n) { | |
return Math.floor(n) + 'px' | |
} | |
/** case convert camelCase to kebab-case */ | |
function kebabCase (str) { | |
return str.replace(/[a-z](?=[A-Z])/g, '$&-').toLowerCase() | |
} | |
/** serialize style object */ | |
function toCssText (obj) { | |
return Object.keys(obj).map(function (key) { | |
return kebabCase(key) + ':' + obj[key] | |
}).join(';') | |
} | |
/** bind event listener */ | |
function on (el, ev, fn) { | |
el.addEventListener(ev, fn) | |
return function off () { el.removeEventListener(ev, fn) } | |
} | |
/** check image element */ | |
function isImage (el) { | |
return el.nodeType === 1 && el.nodeName === 'IMG' | |
} | |
/** delegate dom insertion */ | |
function insert (el) { | |
return document.body.appendChild(el), el | |
} | |
/** delegate dom removal */ | |
function remove (el) { | |
return el.parentNode.removeChild(el) | |
} | |
/** h-ypertext element factory */ | |
function h (tag, obj) { | |
var el = document.createElement(tag) | |
if (obj) { | |
Object.keys(obj).forEach(function (key) { | |
if (key in el) el[key] = obj[key] | |
else el.setAttribute(key, obj[key]) | |
}) | |
} | |
return el | |
} | |
/** | |
* Zoomer - singleton state container | |
* (there can only be one focused image at any given time) | |
*/ | |
var Zoomer = { | |
/** current image element */ | |
img: null, | |
/** element of magnifying glass */ | |
magnifier: null, | |
/** dynamic style sheet element */ | |
styleElement: null, | |
/** cancel event listeners */ | |
cancelListener: null, | |
/** initialize magnifying glass for a given image element */ | |
init: function (img) { | |
var fixedCss = toCssText({ | |
zIndex: 10, | |
position: 'absolute', | |
pointerEvents: 'none', | |
border: '1px double #999', | |
boxShadow: 'inset 1px 1px 3px #ccc, inset -1px -1px 3px #ccc', | |
backgroundRepeat: 'no-repeat', | |
backgroundImage: ['url(', img.src, ')'].join('"') | |
}) | |
this.img = img | |
this.magnifier = insert(h('div', { className: 'zoomer' })) | |
this.styleElement = insert(h('style', { innerHTML: '.zoomer {' + fixedCss + '}' })) | |
this.cancelListener = on(img, 'mousemove', this.onMousemove.bind(this)) | |
}, | |
/** reposition magnifying glass according to mouse position */ | |
onMousemove: function (event) { | |
var img = event.target | |
var mouseX = event.pageX | |
var mouseY = event.pageY | |
// do not hoist for resize support | |
var offX = mouseX - img.offsetLeft | |
var offY = mouseY - img.offsetTop | |
var imgWidth = img.width | |
var imgHeight = img.height | |
var magWidth = imgWidth / 2 | |
var magHeight = imgHeight / 2 | |
// subtract half the magnifier to center it | |
var bgX = (offX * img.naturalWidth / imgWidth) - magWidth/2 | |
var bgY = (offY * img.naturalHeight / imgHeight) - magHeight/2 | |
this.magnifier.style.cssText = toCssText({ | |
left: px(mouseX - magWidth/2), | |
top: px(mouseY - magHeight/2), | |
width: px(magWidth), | |
height: px(magHeight), | |
backgroundPosition: [-bgX, -bgY].map(px).join(' ') | |
}) | |
}, | |
/** cancel event listeners, detach dom elements and nullify references */ | |
reset: function () { | |
this.cancelListener() | |
remove(this.magnifier) | |
remove(this.styleElement) | |
this.img = | |
this.magnifier = | |
this.styleElement = | |
this.cancelListener = null | |
} | |
} | |
/** | |
* use event delegation to limit the widget's area | |
*/ | |
on(document, 'mouseover', function (event) { | |
if (isImage(event.target)) Zoomer.init(event.target) | |
}) | |
/** | |
* since every mouseover event is followed by a mouseout event | |
* there can only be one focused image at any given time | |
*/ | |
on(document, 'mouseout', function (event) { | |
if (event.target === Zoomer.img) Zoomer.reset() | |
}) | |
}()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment