Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 67 additions & 52 deletions assets/gaussholder.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
window.Gaussholder = (function (header) {
/* global StackBlur */
window.Gaussholder = ( function ( header ) {
// Fade duration in ms when the image loads in.
var fadeDuration = 800;

var arrayBufferToBase64 = function( buffer ) {
var arrayBufferToBase64 = function ( buffer ) {
var binary = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
for ( var i = 0; i < len; i++ ) {
binary += String.fromCharCode( bytes[ i ] );
}
return window.btoa( binary );
};

var reconstituteImage = function (header, image) {
var reconstituteImage = function ( header, image ) {
var image_data = image[0],
width = parseInt( image[1] ),
height = parseInt( image[2] );

var full = atob( header.header ) + atob( image_data );
var bytes = new Uint8Array( full.length );
for (var i = 0; i < full.length; i++) {
bytes[i] = full.charCodeAt(i);
for ( var i = 0; i < full.length; i++ ) {
bytes[i] = full.charCodeAt( i );
}

// Poke the bits.
bytes[ header.height_offset ] = ( (height >> 8) & 0xFF);
bytes[ header.height_offset + 1 ] = (height & 0xFF);
bytes[ header.length_offset ] = ( (width >> 8) & 0xFF);
bytes[ header.length_offset + 1] = (width & 0xFF);
bytes[ header.height_offset ] = ( ( height >> 8 ) & 0xFF );
bytes[ header.height_offset + 1 ] = ( height & 0xFF );
bytes[ header.length_offset ] = ( ( width >> 8 ) & 0xFF );
bytes[ header.length_offset + 1] = ( width & 0xFF );

// Back to a full JPEG now.
return arrayBufferToBase64( bytes );
Expand All @@ -40,8 +41,8 @@ window.Gaussholder = (function (header) {
* @param {list} image 3-tuple of base64-encoded image data, width, height
* @param {list} final Final width and height
*/
var render = function (canvas, image, final, cb) {
var ctx = canvas.getContext('2d'),
var render = function ( canvas, image, final, cb ) {
var ctx = canvas.getContext( '2d' ),
width = parseInt( final[0] ),
height = parseInt( final[1] ),
radius = parseInt( final[2] );
Expand All @@ -53,63 +54,77 @@ window.Gaussholder = (function (header) {
ctx.imageSmoothingEnabled = false;

var img = new Image();
img.src = 'data:image/jpg;base64,' + reconstituteImage(header, image);
img.src = 'data:image/jpg;base64,' + reconstituteImage( header, image );
img.onload = function () {
canvas.width = width;
canvas.height = height;

ctx.drawImage(img, 0, 0, width, height);
ctx.drawImage( img, 0, 0, width, height );
StackBlur.canvasRGB( canvas, 0, 0, width, height, radius );
cb();
};
};

/**
* Recalculate image ratio when an image renders on the page.
*
* @param {HTMLImageElement} Image element to recalculate dimensions for.
*/
var calculateDimensionStyle = function ( element ) {
var actual = element.getBoundingClientRect();
var size = element.dataset.gaussholderSize.split( ',' );
var width = parseInt( size[0], 10 ), height = parseInt( size[1], 10 );

if ( actual.width < width ) {
// Rescale, keeping the aspect ratio
height = height * ( actual.width / width );
width = actual.width;
} else if ( actual.height < height ) {
// Rescale, keeping the aspect ratio
width = width * ( actual.height / height );
height = actual.height;
}

element.style.cssText += 'width: ' + width + 'px; height: ' + height + 'px;';
};

/**
* Render placeholder for an image
*
* @param {HTMLImageElement} element Element to render placeholder for
*/
var handleElement = function (element) {
var handleElement = function ( element ) {
if ( ! ( 'gaussholder' in element.dataset ) ) {
return;
}

var canvas = document.createElement('canvas');
var final = element.dataset.gaussholderSize.split(',');
var canvas = document.createElement( 'canvas' );
var final = element.dataset.gaussholderSize.split( ',' );

// Set the dimensions...
element.style.width = final[0] + 'px';
element.style.height = final[1] + 'px';

// ...then recalculate based on what it actually renders as
var original = [ final[0], final[1] ];
if ( element.width < final[0] ) {
// Rescale, keeping the aspect ratio
final[0] = element.width;
final[1] = final[1] * ( final[0] / original[0] );
} else if ( element.height < final[1] ) {
// Rescale, keeping the aspect ratio
final[1] = element.height;
final[0] = final[0] * ( final[1] / original[1] );
}

// Set dimensions, _again_
element.style.width = final[0] + 'px';
element.style.height = final[1] + 'px';
calculateDimensionStyle( element );

render(canvas, element.dataset.gaussholder.split(','), final, function () {
// Schedule an observer to update on any rendering changes.
render( canvas, element.dataset.gaussholder.split( ',' ), final, function () {
// Load in as our background image
element.style.backgroundImage = 'url("' + canvas.toDataURL() + '")';
element.style.backgroundRepeat = 'no-repeat';
});
setTimeout( function () {
calculateDimensionStyle( element );
}, 200 );
} );
};

var loadOriginal = function (element) {
var loadOriginal = function ( element ) {
if ( ! ( 'originalsrc' in element.dataset ) && ! ( 'originalsrcset' in element.dataset ) ) {
return;
}

var data = element.dataset.gaussholderSize.split(','),
var data = element.dataset.gaussholderSize.split( ',' ),
radius = parseInt( data[2] );

// Load our image now
Expand Down Expand Up @@ -147,7 +162,7 @@ window.Gaussholder = (function (header) {
element.style.backgroundRepeat = '';

var start = 0;
var anim = function (ts) {
var anim = function ( ts ) {
if ( ! start ) start = ts;
var diff = ts - start;
if ( diff > fadeDuration ) {
Expand All @@ -160,9 +175,9 @@ window.Gaussholder = (function (header) {
var effectiveRadius = radius * ( 1 - ( diff / fadeDuration ) );

element.style[ filterProp ] = 'blur(' + effectiveRadius * 0.5 + 'px)';
window.requestAnimationFrame(anim);
window.requestAnimationFrame( anim );
};
window.requestAnimationFrame(anim);
window.requestAnimationFrame( anim );
};
};

Expand All @@ -177,55 +192,55 @@ window.Gaussholder = (function (header) {
if ( loopTimeout ) {
return;
}
loopTimeout = window.setTimeout(scrollHandler, 40);
loopTimeout = window.setTimeout( scrollHandler, 40 );
return;
}
lastRun = now;
loopTimeout && (loopTimeout = null);
loopTimeout && ( loopTimeout = null );

var next = [];
for (var i = loadLazily.length - 1; i >= 0; i--) {
for ( var i = loadLazily.length - 1; i >= 0; i-- ) {
var img = loadLazily[i];
var shouldShow = img.getBoundingClientRect().top <= ( window.innerHeight + threshold );
if ( ! shouldShow ) {
next.push(img);
next.push( img );
continue;
}

loadOriginal(img);
loadOriginal( img );
}
loadLazily = next;
if (loadLazily.length < 1) {
window.removeEventListener('scroll', scrollHandler);
if ( loadLazily.length < 1 ) {
window.removeEventListener( 'scroll', scrollHandler );
}
};

/**
* Render all placeholders on the page
*/
var handleAll = function () {
var images = document.getElementsByTagName('img');
var images = document.getElementsByTagName( 'img' );

for (var i = images.length - 1; i >= 0; i--) {
for ( var i = images.length - 1; i >= 0; i-- ) {
var img = images[i];

// Ensure the blank GIF has loaded first
if ( img.complete ) {
handleElement(img);
handleElement( img );
} else {
img.onload = function () {
handleElement(this);
handleElement( this );
}
}
}

loadLazily = images;
scrollHandler();

if (loadLazily.length > 0) {
window.addEventListener('scroll', scrollHandler);
if ( loadLazily.length > 0 ) {
window.addEventListener( 'scroll', scrollHandler );
}
};

return handleAll;
})(window.GaussholderHeader);
} )( window.GaussholderHeader );
Loading