Skip to content

Instantly share code, notes, and snippets.

@jjxtra
Last active February 4, 2023 23:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jjxtra/36ab49364a90e24c0c0ac5ddde19db4a to your computer and use it in GitHub Desktop.
Save jjxtra/36ab49364a90e24c0c0ac5ddde19db4a to your computer and use it in GitHub Desktop.
Don't pay for Cloudflare Pro just to get Mirage, use this script instead to cross fade and lazy load your images. No external js required! https://dailytechnews.io
//
// code used on https://dailytechnews.io to fade images
// MIT license, no support provided :)
//
// Setup:
// - you must put your img tag inside a span tag and set the img tag css to display: none. Make sure the css for the span and img tag has a height set. Width can be 100%.
// - query on all cross-fade spans in a script at the end of your body tag and call DailyTechNewsIoFade(eachSpan) for each one
// - i.e. _loop('.cross-fade', DailyTechNewsIoFade);
// - required html markup: <span class='cross-fade'><img class='your-image-class' src='/path/lowres/image.webp' title='title' alt='alt' data-src='/path/highres/image.webp' data-dim='1.5' /></span>
// - the src is a very low res image (32x32 max dimensions, with full res image aspect ratio as close as possible)
// - title and alt are up to you
// - data-dim is width/height of full res image
// - data-src is full image source
//
// javascript helpers to avoid using jquery
const _win = window;
const _doc = document;
const _qs = s => _doc.querySelector(s);
const _qsa = s => _doc.querySelectorAll(s);
const _ga = (o, a) => o.getAttribute(a); // object, attribute name
const _sa = (o, a, v) => o.setAttribute(a, v); // object, attribute name, value
const _ra = (o, a) => o.removeAttribute(a); // object, attribute name
const _ae = (o, e, f) => o.addEventListener(e, f); // object, event name, function
const _gi = i => _doc.getElementById(i); // id
const _ce = (t, h = '') => { var e = _doc.createElement(t); e.innerHTML = h; return e; } // tag, html
const _eu = c => encodeURIComponent(c); // string
const _hide = e => { e.style.visibility = 'hidden'; e.style.display = 'none'; } // element
const _show = e => { e.style.visibility = 'visible'; e.style.display = e.tagName == 'div' ? 'block' : ''; } // element
const _st = setTimeout; // shortcut for setTimeout
const _si = setInterval; // shortcut for setInterval
// loop through array, or if a is a string, loop through matching selector elements
const _loop = (a, f) => { a = typeof a == 'string' ? _qsa(a) : a; for (var i = 0; i < a.length; i++) { f(a[i], i); } }
_win.isInViewport = obj =>
{
var elementTop = obj.offsetTop;
var elementBottom = elementTop + obj.offsetHeight;
var wh = _win.innerHeight;
// total area is one vh up from top of viewport and 2vh down
var viewportTop = window.scrollY - wh;
var viewportBottom = viewportTop + wh + wh + wh;
return elementBottom > viewportTop && elementTop < viewportBottom;
};
// img is the span containing your img tag
function DailyTechNewsIoFade(img)
{
const crossFades = [0, 1000]; // instant and normal fade (ms)
var loaded = false;
var crossFadeIndex = 0;
var oi = img.firstChild;
var alt = _ga(oi, 'alt');
var ds = _ga(oi, 'data-src');
// if you want to dynamically change the source based on the screen width or height, you could do so here by changing the ds var
var dim = _ga(oi, 'data-dim'); // width/height ratio, needs to be known to keep low res image the right bounds
var cl = _ga(oi, 'class');
var ti = _ga(oi, 'title');
var dimFloat = parseFloat(dim);
var loading = 'lazy';
var priority = '';
var visibleFold = _win.isInViewport(img);
if (visibleFold)
{
// no lazy and high priority if in or near fold
priority = 'high';
loading = '';
}
_ra(oi, 'data-sric');
_ra(oi, 'data-dim');
_ra(oi, 'alt');
// keep the low res image the right dimensions
function oiResize()
{
var width = img.offsetWidth;
var height = img.offsetHeight;
var widthAspect = Math.min(width, (height * dimFloat));
widthAspect = Math.round(widthAspect * 2) / 2;
widthAspect = widthAspect.toFixed(1);
_sa(oi, 'width', widthAspect);
_sa(oi, 'height', height);
img.style.width = widthAspect;
img.style.height = height;
};
function oiFade()
{
crossFadeIndex = 1;
oi.style.filter = 'blur(0.5rem)';
oi.style['mixed-blend-mode'] = 'plus-lighter';
_show(oi);
};
oiResize();
_ae(oi, 'resize', oiResize);
// create new image tag for the final image
var ni = new Image;
ni.style['z-index'] = 500;
ni.style.opacity = 0; // keep hidden until we load
_show(ni);
ni.className = cl;
_sa(ni, 'title', ti);
_sa(ni, 'alt', alt);
if (priority.length != 0)
{
ni.fetchpriority = priority;
}
if (loading.length != 0)
{
ni.loading = loading;
}
// image load event
function imgLoad()
{
if (loaded)
{
return;
}
loaded = true;
_ra(ni, 'load');
_ra(ni, 'error');
_ra(ni, 'alt');
_ra(ni, 'title');
_ra(ni, 'data-cfidx');
var animationTime = crossFades[crossFadeIndex];
function animationCompletion()
{
oi.remove();
ni.style.opacity = '1';
ni.style.transition = '';
ni.parentElement.style.width = '';
ni.parentElement.style.height = '';
};
if (animationTime <= 100)
{
animationCompletion();
}
else
{
// fade in new final img
ni.style.transition = `opacity ${animationTime}ms`;
ni.style.opacity = '1';
animationTime *= 1.5;
oi.style.transition = `opacity ${animationTime}ms`;
oi.style.opacity = '0';
_st(animationCompletion, animationTime);
}
};
// image error event
function imgError()
{
// failure, I guess the low res img is all they get :|
ni.remove();
};
// set events
_ae(ni, 'load', imgLoad);
_ae(ni, 'error', imgError);
ni.src = ds;
img.appendChild(ni);
if (ni.complete)
{
imgLoad();
}
else
{
// setup cross fade from previous img tag, if more than 500 milliseconds, we put in the low image and cross fade from it to the full image
_st(oiFade, 500);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment