import { $$, clearEl } from './utils/dom'; import store from './utils/store'; const imageVersions = { // [width, height] small: [320, 240], medium: [800, 600], large: [1280, 1024] }; /** * Picks the appropriate image version for a given width and height * of the viewport and the image dimensions. */ function selectVersion(imageWidth, imageHeight, imageSize, imageMime) { let viewWidth = document.documentElement.clientWidth, viewHeight = document.documentElement.clientHeight; // load hires if that's what you asked for if (store.get('serve_hidpi')) { viewWidth *= window.devicePixelRatio || 1; viewHeight *= window.devicePixelRatio || 1; } if (viewWidth > 1024 && imageHeight > 1024 && imageHeight > 2.5 * imageWidth) { // Treat as comic-sized dimensions.. return 'tall'; } // Find a version that is larger than the view in one/both axes // .find() is not supported in older browsers, using a loop for (let i = 0, versions = Object.keys(imageVersions); i < versions.length; ++i) { const version = versions[i], dimensions = imageVersions[version], versionWidth = Math.min(imageWidth, dimensions[0]), versionHeight = Math.min(imageHeight, dimensions[1]); if (versionWidth > viewWidth || versionHeight > viewHeight) { return version; } } // If the view is larger than any available version, display the original image. // // Sanity check to make sure we're not serving unintentionally huge assets // all at once (where "huge" > 25 MiB). Videos are loaded in chunks so it // doesn't matter too much there. if (imageMime === 'video/webm' || imageSize <= 26214400) { return 'full'; } return 'large'; } /** * Given a target container element, chooses and scales an image * to an appropriate dimension. */ function pickAndResize(elem) { const imageWidth = parseInt(elem.dataset.width, 10), imageHeight = parseInt(elem.dataset.height, 10), imageSize = parseInt(elem.dataset.imageSize, 10), imageMime = elem.dataset.mimeType, scaled = elem.dataset.scaled, uris = JSON.parse(elem.dataset.uris); let version = 'full'; if (scaled === 'true') { version = selectVersion(imageWidth, imageHeight, imageSize, imageMime); } const uri = uris[version]; let imageFormat = /\.(\w+?)$/.exec(uri)[1]; if (version === 'full' && store.get('serve_webm') && Boolean(uris.mp4)) { imageFormat = 'mp4'; } // Check if we need to change to avoid flickering if (imageFormat === 'mp4' || imageFormat === 'webm') { for (const sourceEl of elem.querySelectorAll('video source')) { if (sourceEl.src.endsWith(uri) || (imageFormat === 'mp4' && sourceEl.src.endsWith(uris.mp4))) return; } // Scrub out the target element. clearEl(elem); } const muted = store.get('unmute_videos') ? '' : 'muted'; if (imageFormat === 'mp4') { elem.classList.add('full-height'); elem.insertAdjacentHTML('afterbegin', `` ); } else if (imageFormat === 'webm') { elem.insertAdjacentHTML('afterbegin', `` ); const video = elem.querySelector('video'); if (scaled === 'true') { video.className = 'image-scaled'; } else if (scaled === 'partscaled') { video.className = 'image-partscaled'; } } else { let image; if (scaled === 'true') { image = ``; } else if (scaled === 'partscaled') { image = ``; } else { image = ``; } if (elem.innerHTML === image) return; clearEl(elem); elem.insertAdjacentHTML('afterbegin', image); } } /** * Bind an event to an image container for updating an image on * click/tap. */ function bindImageForClick(target) { target.addEventListener('click', () => { if (target.getAttribute('data-scaled') === 'true') { target.setAttribute('data-scaled', 'partscaled'); } else if (target.getAttribute('data-scaled') === 'partscaled') { target.setAttribute('data-scaled', 'false'); } else { target.setAttribute('data-scaled', 'true'); } pickAndResize(target); }); } function bindImageTarget(node = document) { $$('.image-target', node).forEach(target => { pickAndResize(target); if (target.dataset.mimeType === 'video/webm') { // Don't interfere with media controls on video return; } bindImageForClick(target); window.addEventListener('resize', () => { pickAndResize(target); }); }); } export { bindImageTarget };