Created
September 12, 2024 09:27
-
-
Save ijurko/5b3cb6dd2fc69199bdfc7eda76b19a86 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { gsap } from "gsap"; | |
import { ScrollTrigger } from "gsap/dist/ScrollTrigger"; | |
gsap.registerPlugin(ScrollTrigger); | |
export default class ImageSequence { | |
constructor() { | |
this.DOM = { | |
chapterNavigation: ".js-chapter-navigation-wrapper", | |
sequence: ".js-image-sequence", | |
sequenceWrapper: ".js-image-sequence-wrapper", | |
canvasWrapper: ".js-image-sequence-canvas-wrapper", | |
step: ".js-sequence-step", | |
}; | |
} | |
init() { | |
this.sequenceWrapper = document.querySelector(this.DOM.sequenceWrapper); | |
this.chapterNavigation = document.querySelector(this.DOM.chapterNavigation); | |
if (!this.sequenceWrapper) { | |
return; | |
} | |
if (this.chapterNavigation) { | |
this.chapterNavigationToggle(); | |
} | |
this.sequence = document.querySelector(this.DOM.sequence); | |
this.canvasWrapper = document.querySelector(this.DOM.canvasWrapper); | |
gsap.set("html", { | |
"--canvas-height": `${this.canvasWrapper.clientHeight}px`, | |
}); | |
// set scroll position to top of the document | |
window.scrollTo(0, 0); | |
if ("scrollRestoration" in window.history) { | |
window.history.scrollRestoration = "manual"; | |
} | |
this.loaded = false; | |
this.frameIndex = 0; | |
this.imagesArray = []; | |
this.frameCount = 360; | |
let device = "mobile"; | |
if (window.matchMedia("(min-width: 800px)").matches) { | |
this.frameCount = 360; | |
device = "desktop"; | |
} | |
for (let i = 0; i <= this.frameCount; i++) { | |
this.imagesArray.push({ url: `${window.sequence}/${device}/${String(i).padStart(4, "0")}_result.avif?v=1` }); | |
} | |
if (this.imagesArray && this.imagesArray.length > 0) { | |
this.steps = gsap.utils.toArray(this.DOM.step); | |
this.stepsLength = this.steps.length; | |
this.segmentsLoaded = 0; | |
// don't squish img when resized | |
window.addEventListener("resize", () => this.resize()); | |
this.canvasLoad(); | |
this.sequenceController(); | |
} | |
} | |
chapterNavigationToggle() { | |
ScrollTrigger.create({ | |
ignoreMobileResize: true, | |
trigger: this.sequenceWrapper, | |
start: "top top+=1px", | |
end: "bottom bottom", | |
onEnter: () => { | |
gsap.to(this.chapterNavigation, { | |
autoAlpha: 0, | |
}); | |
}, | |
onEnterBack: () => { | |
gsap.to(this.chapterNavigation, { | |
autoAlpha: 0, | |
}); | |
}, | |
onLeave: () => { | |
gsap.to(this.chapterNavigation, { | |
autoAlpha: 1, | |
}); | |
}, | |
}); | |
} | |
canvasLoad() { | |
this.context = this.sequence.getContext("2d"); | |
this.context.imageSmoothingEnabled = true; | |
// for retina screens | |
this.retinaScale(); | |
// num of images | |
this.frameCount = this.imagesArray.length; | |
this.framesLoaded = 0; | |
// initial image load | |
this.img = new Image(); | |
this.img.src = this.currentFrame(0); | |
this.sequence.width = this.canvasWrapper.offsetWidth; | |
this.sequence.height = this.canvasWrapper.offsetHeight; | |
this.img.onload = () => { | |
this.drawImage(this.img); | |
}; | |
// num of images in single chunk - for preload sequence | |
this.singleChunk = Math.floor(this.frameCount / (this.stepsLength + 1)); | |
this.images = []; | |
this.preloadImages(); | |
} | |
preloadImages() { | |
if (this.segmentsLoaded < this.stepsLength + 1) { | |
for (let i = this.singleChunk * this.segmentsLoaded; i < this.singleChunk * (this.segmentsLoaded + 1); i++) { | |
const img = new Image(); | |
img.src = this.currentFrame(i); | |
const imageProps = [img, i]; | |
this.images.push(imageProps); | |
img.onload = () => { | |
this.framesLoaded += 1; | |
if (this.framesLoaded > 0) { | |
this.progressController(); | |
} | |
}; | |
} | |
this.segmentsLoaded++; | |
setTimeout(() => { | |
this.preloadImages(); | |
}, 500); | |
} | |
} | |
/** | |
* | |
* @param {number} index | |
* @returns {string} | |
*/ | |
currentFrame(index) { | |
return `${this.imagesArray[index].url}`; | |
} | |
/** | |
* | |
* @param {HTMLImageElement} img | |
*/ | |
drawImage(img) { | |
if (img != null) { | |
this.context.drawImage(img, 0, 0, this.sequence.width, this.sequence.height); | |
} | |
} | |
/** | |
* | |
* @param {number} index | |
*/ | |
updateImage(index) { | |
if (this.images[index] != null) { | |
this.drawImage(this.images[index][0]); | |
} | |
} | |
sequenceController() { | |
let scrollDirection = 1; | |
this.steps.forEach((step, i) => { | |
const inc = 1 / (this.stepsLength - 1); | |
this.scrollInteractions(inc, scrollDirection, i, step); | |
}); | |
} | |
/** | |
* | |
* @param {number} inc | |
* @param {number} scrollDirection | |
* @param {number} i | |
* @param {HTMLElement} step | |
*/ | |
scrollInteractions(inc, scrollDirection, i, step) { | |
let trigger = step; | |
// if the step is pinned inside ScrollTrigger | |
if (step.parentNode.classList.contains("pin-spacer")) { | |
trigger = step.parentNode; | |
} | |
let starting; | |
let ending = "bottom bottom"; | |
if (i === 0) { | |
starting = "top top"; | |
} else { | |
starting = "top bottom"; | |
} | |
const stepsDivider = this.stepsLength - 1; | |
ScrollTrigger.create({ | |
ignoreMobileResize: true, | |
trigger: trigger, | |
start: starting, | |
end: ending, | |
onUpdate: (self) => { | |
let progress = 0; | |
if (this.stepsLength > 0) { | |
progress = (i - 1) / stepsDivider + self.progress * inc; | |
} | |
this.frameIndex = Math.floor(progress * this.frameCount); | |
this.updateImage(this.frameIndex); | |
}, | |
}); | |
} | |
progressController() { | |
const frameCount = this.frameCount / this.stepsLength - 1; | |
const progress = Math.floor((100 / frameCount) * this.framesLoaded); | |
if (progress < 100) { | |
// console.log(progress); | |
} else if (progress >= 100 && !this.loaded) { | |
console.log("Images for first section are loaded!"); | |
this.loaded = true; | |
gsap.to(this.sequenceWrapper, { | |
autoAlpha: 1, | |
}); | |
} | |
} | |
resize() { | |
this.sequence.width = this.canvasWrapper.clientWidth; | |
this.sequence.height = this.canvasWrapper.clientHeight; | |
this.retinaScale(); | |
this.updateImage(this.frameIndex); | |
} | |
retinaScale() { | |
// for retina screens | |
if (window.devicePixelRatio !== 1) { | |
const width = this.canvasWrapper.clientWidth; | |
const height = this.canvasWrapper.clientHeight; | |
// scale the canvas by window.devicePixelRatio | |
this.sequence.setAttribute("width", `${width}`); | |
this.sequence.setAttribute("height", `${height}`); | |
// use css to bring it back to regular size | |
this.sequence.setAttribute("style", `width: ${width}px; height: ${height}px;`); | |
// set the scale of the context | |
// this.sequence.getContext("2d").scale(window.devicePixelRatio, window.devicePixelRatio); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment