Skip to content

Instantly share code, notes, and snippets.

@bryanberger
Last active October 22, 2021 23:41
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 bryanberger/38cdc44defa2692bb73e79c62fdc112c to your computer and use it in GitHub Desktop.
Save bryanberger/38cdc44defa2692bb73e79c62fdc112c to your computer and use it in GitHub Desktop.
GSAP ScrollTrigger image sequence with optimized alpha masking (perf)
/**
*
* @param {HTMLImageElement} rgbImage preferably a JPG
* @param {HTMLImageElement} alphaImage preferably a PNG with alpha channel
* @param {HTMLCanvasElement} canvas optional
*/
export const mergeAlpha = (rgbImage, alphaImage, canvas) => {
/* eslint-disable no-param-reassign */
if (!canvas) {
canvas = document.createElement('canvas');
}
canvas.width = Math.max(alphaImage.width, rgbImage.width);
canvas.height = Math.max(alphaImage.height, rgbImage.height);
const context = canvas.getContext('2d');
context.save();
context.drawImage(rgbImage, 0, 0);
context.globalCompositeOperation = 'destination-in';
context.drawImage(alphaImage, 0, 0);
context.restore();
return canvas;
};
setupLaptopCanvas() {
const [images, masks] = this.laptopImages;
const canvas = this.elements.laptopCanvas;
const context = canvas.getContext('2d');
const video = this.elements.videos.laptopSlack;
const state = { curFrame: 0 };
// Merge all rgb JPGs with a PNG alpha channel to reduce filesize and provide us transparency.
const mergedImages = images.map((image, index) => mergeAlpha(images[index], masks[index]));
// Always resume play when `entering back` (otherwise the video will be auto-paused).
ScrollTrigger.create({
trigger: '.trigger-2',
onEnter: () => video.play(),
onEnterBack: () => video.play(),
});
const sequence = gsap.timeline({
delay: 0.1,
scrollTrigger: {
trigger: '.trigger-2',
endTrigger: '.trigger-4',
start: 'top center',
end: 'center top',
toggleActions: 'play reverse play reverse',
},
});
sequence
.to(state, {
curFrame: images.length - 1,
roundProps: 'curFrame', // Use only integers, so they can be used as array indices
immediateRender: true, // load first image automatically
ease: 'none',
onUpdate: () => {
const mergedImage = mergedImages[state.curFrame];
// Clear the last frame
context.clearRect(0, 0, canvas.width, canvas.height);
// Draw mergedImage
context.drawImage(mergedImage, 0, 0);
},
})
.to(video, {
autoAlpha: 1,
duration: 0.75,
ease: 'none',
onComplete: () => video.play(),
onStart: () => video.load(),
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment