Skip to content

Instantly share code, notes, and snippets.

@jlieb10
Last active January 22, 2019 19:31
Show Gist options
  • Save jlieb10/6ec66baa892cb2cdde85507d52d4781f to your computer and use it in GitHub Desktop.
Save jlieb10/6ec66baa892cb2cdde85507d52d4781f to your computer and use it in GitHub Desktop.
Image Enlarger
/////////////////////////////////////////////
// Image Enlarger Module
//
// loc: src/js/modules/image_enlarger.js
//
// Module that finds images in the DOM and
// duplicates them dynamically (on mouseover)
// with their larger versions which are
// displayed dynamically (on click) to
// drastically reduce load time on
// HD image-heavy articles
////////////////////////////////////////////
class ImageEnlarger {
constructor() {
// track state
this.active = false;
this.currentScroll = null;
// get nodes from the DOM & using Array prototype to convert node collections to arrays
this.images = [].slice.call(document.querySelectorAll('.inline-large, .inline-xlarge'));
this.article = document.querySelector('.post-article');
this.navs = [].slice.call(document.querySelectorAll('.navbar-brands-container, .reading-bar'));
}
init() {
this.setListeners();
this.processPoster();
}
// Test article to see if it has a poster to enlarge and then to set proper listeners & class on the poster
processPoster() {
let test = document.querySelector('.fc-header .poster-wrapper :nth-child(1)');
if (test) {
test = test.tagName.match(/(IMG|NOSCRIPT)/);
}
if (test && test.length > 0) {
let imageWrapper = document.createElement('div');
let overlay = this.createOverlay();
let poster = document.querySelector('.fc-header .poster-wrapper');
let posterImg = poster.querySelector('img');
imageWrapper.setAttribute('class', 'image-wrapper');
poster.classList.add('inline-large');
imageWrapper.appendChild(posterImg);
imageWrapper.appendChild(overlay);
if (poster.querySelector('figcaption')) {
poster.insertBefore(imageWrapper, poster.querySelector('figcaption'));
} else {
poster.appendChild(imageWrapper);
}
poster.addEventListener('click', function() {
this.clickHandle(posterImg);
}.bind(this));
}
}
// Set hover listeners on all inline-large images within post
setListeners() {
this.images.forEach(function(figure) {
figure.on('mouseover', function(e) {
// return if already triggered before
if (e.currentTarget.dataset.triggered) return;
e.currentTarget.dataset.triggered = true;
this.hoverHandle(figure);
}.bind(this));
}.bind(this));
}
// On hover, replace the small image with its full-size version
hoverHandle(figure) {
let rgx = /\/imagecache\/inline-(large|xlarge)/i;
let wrapper = figure.querySelector('.image-wrapper') || figure;
let img = wrapper.querySelector('img');
let cloneSrc = img.getAttribute('src').replace(rgx, '');
let overlay = this.createOverlay();
let newImage = document.createElement('img');
newImage.setAttribute('src', cloneSrc);
newImage.onload = function() {
wrapper.insertBefore(newImage, img);
wrapper.removeChild(img);
wrapper.appendChild(overlay);
figure.addEventListener('click', function() {
this.clickHandle(newImage);
}.bind(this));
}.bind(this);
}
// Direct click behavior depending on state of image (enlarged or not)
clickHandle(clone) {
if (this.active) {
this.rmHandle(clone);
return;
}
this.active = true;
this.currentScroll = function() {
this.scrollHandle(clone);
}.bind(this);
document.addEventListener('scroll', this.currentScroll);
this.getTranslationPoints(clone);
}
// Direct scroll behavior depending on state of image (enlarged or not)
scrollHandle(clone) {
if (this.active) {
this.rmHandle(clone);
return;
}
return;
}
// On scroll or click, if the image is enlarged, get rid of enlargement
rmHandle(clone) {
let navBar = document.querySelector('.nav-bar-wrapper');
let figure = clone.parentNode.parentNode;
document.removeEventListener('scroll', this.currentScroll);
clone.style.transform = 'scale(1) translate(0, 0)';
figure.classList.remove('enlarged');
navBar.style.position = 'relative';
this.article.style.overflow = 'hidden';
this.active = false;
}
// Find where the image is relative to viewport and set its translation points to center it when enlarged
getTranslationPoints(clone) {
let figure = clone.parentNode.parentNode;
let scrollTop = document.body.scrollTop;
let cloneOffset = clone.getBoundingClientRect();
let viewportCenterX = (window.innerWidth / 2);
let viewportCenterY = scrollTop + (window.innerHeight / 2);
let imageCenterX = cloneOffset.left + (clone.width / 2);
let imageCenterY = (cloneOffset.top + scrollTop) + (clone.height / 2);
let xTranslate = (viewportCenterX - imageCenterX);
let yTranslate = (viewportCenterY - imageCenterY);
let scaleFactor = this.getScaleFactor(clone);
let cloneTransform = 'translate(' + xTranslate + 'px, ' + yTranslate + 'px) scale(' + scaleFactor + ')';
clone.style.transform = cloneTransform;
this.article.style.overflow = 'visible';
this.animateNavs();
figure.classList.add('enlarged');
}
// Find and compare image's natural aspect ratio with window's and set its scale factor accordingly
// If the image is smaller than the window, enlarge image to its maximum size
getScaleFactor(clone) {
let imgRatio = (clone.width / clone.height);
let windowRatio = (window.innerWidth / window.innerHeight);
let scaleFactor = window.innerHeight / clone.height;
if ((clone.naturalHeight < window.innerHeight) && (clone.naturalWidth < window.innerWidth)) {
scaleFactor = clone.naturalWidth / clone.width;
return scaleFactor;
}
if (imgRatio > windowRatio) {
scaleFactor = window.innerWidth / clone.width;
return scaleFactor;
}
return scaleFactor;
}
// Simple function to create overlay on either poster or
createOverlay() {
let overlay = document.createElement('div');
overlay.setAttribute('class', 'overlay');
return overlay;
}
// Animate navs out of view when image is enlarged, depending on if the user has scrolled beyond the top ad or not
animateNavs() {
let navBar = document.querySelector('.nav-bar-wrapper');
let adHeight = navBar.querySelector('.ad-wrapper').offsetHeight;
let currentY = window.scrollY;
navBar.style.position = 'static';
if (adHeight < currentY) {
this.navs.forEach(function(nav) {
nav.style.top = '-60px';
});
}
}
// First function called in controller, initialize if screen is at least 1024px wide
on() {
if (window.innerWidth > 1023) {
window.addEventListener('DOMContentLoaded', this.init.bind(this));
}
}
}
export default new ImageEnlarger();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment