UNPKG

@tholman/intense-images

Version:

A simple library to view large images up close using simple mouse interaction, and the full screen.

418 lines (358 loc) 14.2 kB
window.requestAnimFrame = (function() { return ( window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); } ); })(); window.cancelRequestAnimFrame = (function() { return ( window.cancelAnimationFrame || window.webkitCancelRequestAnimationFrame || window.mozCancelRequestAnimationFrame || window.oCancelRequestAnimationFrame || window.msCancelRequestAnimationFrame || clearTimeout ); })(); var Intense = (function() { "use strict"; var KEYCODE_ESC = 27; // Track both the current and destination mouse coordinates // Destination coordinates are non-eased actual mouse coordinates var mouse = { xCurr: 0, yCurr: 0, xDest: 0, yDest: 0 }; var horizontalOrientation = true; var invertInteractionDirection = false; // Holds the animation frame id. var looper; // Single image var image; // Current position of scrolly element var lastPosition, currentPosition = 0; var sourceDimensions, target; var targetDimensions = { w: 0, h: 0 }; var container; var containerDimensions = { w: 0, h: 0 }; var overflowArea = { x: 0, y: 0 }; // Overflow variable before screen is locked. var overflowValue; var active = false; /* ------------------------- /* UTILS /* -------------------------*/ // Soft object augmentation function extend(target, source) { for (var key in source) if (!(key in target)) target[key] = source[key]; return target; } // Applys a dict of css properties to an element function applyProperties(target, properties) { for (var key in properties) { target.style[key] = properties[key]; } } // Returns whether target a vertical or horizontal fit in the page. // As well as the right fitting width/height of the image. function getFit(source) { var heightRatio = window.innerHeight / source.h; if (source.w * heightRatio > window.innerWidth) { return { w: source.w * heightRatio, h: source.h * heightRatio, fit: true }; } else { var widthRatio = window.innerWidth / source.w; return { w: source.w * widthRatio, h: source.h * widthRatio, fit: false }; } } /* ------------------------- /* APP /* -------------------------*/ function startTracking(passedElements) { var i; // If passed an array of elements, assign tracking to all. if (passedElements.length) { // Loop and assign for (i = 0; i < passedElements.length; i++) { track(passedElements[i]); } } else { track(passedElements); } } function track(element) { // Element needs a src at minumun. if (element.getAttribute("data-image") || element.src || element.href) { element.addEventListener( "click", function(e) { if (element.tagName === "A") { e.preventDefault(); } if (!active) { init(this); } }, false ); } } function start() { loop(); } function stop() { cancelRequestAnimFrame(looper); } function loop() { looper = requestAnimFrame(loop); positionTarget(); } // Lock scroll on the document body. function lockBody() { overflowValue = document.body.style.overflow; document.body.style.overflow = "hidden"; } // Unlock scroll on the document body. function unlockBody() { document.body.style.overflow = overflowValue; } function setState(element, newClassName) { if (element) { element.className = element.className.replace("intense--loading", ""); element.className = element.className.replace("intense--viewing", ""); element.className += " " + newClassName; } else { // Remove element with class .view var elems = document.querySelectorAll(".intense--viewing"); [].forEach.call(elems, function(el) { el.className = el.className.replace("intense--viewing", "").trim(); }); } } function createViewer(title, caption) { /* * Container */ var containerProperties = { backgroundColor: "rgba(0,0,0,0.8)", width: "100%", height: "100%", position: "fixed", top: "0px", left: "0px", overflow: "hidden", zIndex: "999999", margin: "0px", webkitTransition: "opacity 150ms cubic-bezier( 0, 0, .26, 1 )", MozTransition: "opacity 150ms cubic-bezier( 0, 0, .26, 1 )", transition: "opacity 150ms cubic-bezier( 0, 0, .26, 1 )", webkitBackfaceVisibility: "hidden", opacity: "0" }; container = document.createElement("figure"); container.appendChild(target); applyProperties(container, containerProperties); var imageProperties = { cursor: 'url( "data:image/png;base64,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" ) 25 25, no-drop' }; applyProperties(target, imageProperties); /* * Caption Container */ var captionContainerProperties = { fontFamily: 'Georgia, Times, "Times New Roman", serif', position: "fixed", bottom: "0px", left: "0px", padding: "20px", color: "#fff", wordSpacing: "0.2px", webkitFontSmoothing: "antialiased", textShadow: "-1px 0px 1px rgba(0,0,0,0.4)" }; var captionContainer = document.createElement("figcaption"); applyProperties(captionContainer, captionContainerProperties); /* * Caption Title */ if (title) { var captionTitleProperties = { margin: "0px", padding: "0px", fontWeight: "normal", fontSize: "40px", letterSpacing: "0.5px", lineHeight: "35px", textAlign: "left" }; var captionTitle = document.createElement("h1"); applyProperties(captionTitle, captionTitleProperties); captionTitle.innerHTML = title; captionContainer.appendChild(captionTitle); } if (caption) { var captionTextProperties = { margin: "0px", padding: "0px", fontWeight: "normal", fontSize: "20px", letterSpacing: "0.1px", maxWidth: "500px", textAlign: "left", background: "none", marginTop: "5px" }; var captionText = document.createElement("h2"); applyProperties(captionText, captionTextProperties); captionText.innerHTML = caption; captionContainer.appendChild(captionText); } container.appendChild(captionContainer); setDimensions(); mouse.xCurr = mouse.xDest = window.innerWidth / 2; mouse.yCurr = mouse.yDest = window.innerHeight / 2; document.body.appendChild(container); setTimeout(function() { container.style["opacity"] = "1"; }, 10); } function removeViewer() { unlockBody(); unbindEvents(); stop(); document.body.removeChild(container); active = false; setState(false); } function setDimensions() { // Manually set height to stop bug where var imageDimensions = getFit(sourceDimensions); target.width = imageDimensions.w; target.height = imageDimensions.h; horizontalOrientation = imageDimensions.fit; targetDimensions = { w: target.width, h: target.height }; containerDimensions = { w: window.innerWidth, h: window.innerHeight }; overflowArea = { x: containerDimensions.w - targetDimensions.w, y: containerDimensions.h - targetDimensions.h }; } function init(element) { setState(element, "intense--loading"); var imageSource = element.getAttribute("data-image") || element.src || element.href; var title = element.getAttribute("data-title") || element.title; var caption = element.getAttribute("data-caption"); // Clear old onload message if (image) { image.onload = null; } image = new Image(); image.onload = function() { sourceDimensions = { w: image.width, h: image.height }; // Save original dimensions for later. target = this; createViewer(title, caption); lockBody(); bindEvents(); loop(); setState(element, "intense--viewing"); }; image.src = imageSource; } function bindEvents() { container.addEventListener("mousemove", onMouseMove, false); container.addEventListener("touchmove", onTouchMove, false); window.addEventListener("resize", setDimensions, false); window.addEventListener("keyup", onKeyUp, false); target.addEventListener("click", removeViewer, false); } function unbindEvents() { container.removeEventListener("mousemove", onMouseMove, false); container.removeEventListener("touchmove", onTouchMove, false); window.removeEventListener("resize", setDimensions, false); window.removeEventListener("keyup", onKeyUp, false); target.removeEventListener("click", removeViewer, false); } function onMouseMove(event) { mouse.xDest = event.clientX; mouse.yDest = event.clientY; } function onTouchMove(event) { event.preventDefault(); // Needed to keep this event firing. mouse.xDest = window.innerWidth - event.touches[0].clientX; mouse.yDest = window.innerHeight - event.touches[0].clientY; } // Exit on excape key pressed; function onKeyUp(event) { event.preventDefault(); if (event.keyCode === KEYCODE_ESC) { removeViewer(); } } function positionTarget() { mouse.xCurr += (mouse.xDest - mouse.xCurr) * 0.05; mouse.yCurr += (mouse.yDest - mouse.yCurr) * 0.05; if (horizontalOrientation === true) { // HORIZONTAL SCANNING currentPosition += mouse.xCurr - currentPosition; if (mouse.xCurr !== lastPosition) { var position = parseFloat( calcPosition(currentPosition, containerDimensions.w) ); position = overflowArea.x * position; target.style["webkitTransform"] = "translate(" + position + "px, 0px)"; target.style["MozTransform"] = "translate(" + position + "px, 0px)"; target.style["msTransform"] = "translate(" + position + "px, 0px)"; lastPosition = mouse.xCurr; } } else if (horizontalOrientation === false) { // VERTICAL SCANNING currentPosition += mouse.yCurr - currentPosition; if (mouse.yCurr !== lastPosition) { var position = parseFloat( calcPosition(currentPosition, containerDimensions.h) ); position = overflowArea.y * position; target.style["webkitTransform"] = "translate( 0px, " + position + "px)"; target.style["MozTransform"] = "translate( 0px, " + position + "px)"; target.style["msTransform"] = "translate( 0px, " + position + "px)"; lastPosition = mouse.yCurr; } } function calcPosition(current, total) { return invertInteractionDirection ? (total - current) / total : current / total; } } function config(options) { if ("invertInteractionDirection" in options) invertInteractionDirection = options.invertInteractionDirection; } function main(element, configOptions) { // Parse arguments if (!element) { throw "You need to pass an element!"; } // If they have a config, use it! if (configOptions) { config(configOptions); } startTracking(element); } return extend(main, { resize: setDimensions, start: start, stop: stop, config: config }); })(); if (typeof module !== "undefined" && module.exports) { module.exports = Intense; }