I am trying to build a webpage that display images related to the scroll. I am able to display content on top of the canvas and the animation worked fine.
What I am not able to do is add content AFTER the canvas. Once the canvas ends, and so does the window, I want to display <h2>
. A scroll bar should appear when the page is loaded and I should be able to scroll a bit more so that I see the <h2>
.
Any ideas?
const html = document.documentElement;
const canvas = document.querySelector('.ultragear-scrolling');
const context = canvas.getContext('2d');
const overlay = document.querySelector('.overlay');
const message = document.querySelector('.message');
const content = document.querySelector('.content');
const currentFrame = index => (
`https://gi.esmplus.com/BLADER01/Hyuk/oled-moon/${index.toString().padStart(0, '1')}.jpg`
);
// frameCount= Number of image
const frameCount = 294;
// set canvas dimensions
canvas.height = 770;
canvas.width = 1158;
const images = [];
// Preload images
const preloadImages = () => {
for (let i = 1; i <= frameCount; i++) {
const img = new Image();
img.src = currentFrame(i);
images.push(img);
}
};
preloadImages();
// Create, load, and draw the image
const img = new Image();
img.src = currentFrame(1);
img.onload = function() {
drawCentered(img);
}
const updateImage = index => {
// Clear the previous image
context.clearRect(0, 0, canvas.width, canvas.height);
// Draw the new image
drawCentered(images[index]);
};
function drawCentered(img) {
const offsetX = (canvas.width - img.width) / 2;
const offsetY = (canvas.height - img.height) / 2;
context.drawImage(img, offsetX, offsetY);
}
let animationFrameId;
window.addEventListener('scroll', () => {
cancelAnimationFrame(animationFrameId);
const scrollTop = html.scrollTop;
const maxScrollTop = html.scrollHeight - window.innerHeight;
const scrollFraction = scrollTop / maxScrollTop;
const frameIndex = Math.min(
frameCount - 1,
Math.floor(scrollFraction * frameCount)
);
animationFrameId = requestAnimationFrame(() => updateImage(frameIndex));
if (frameIndex === frameCount - 1) {
canvas.style.transition = 'opacity 1s ease-in-out';
canvas.style.opacity = '0';
overlay.classList.add('visible');
message.classList.add('visible');
// Delay showing the content to ensure the message is fully visible
setTimeout(() => {
content.classList.add('visible');
}, 1000); // Adjust delay as needed
} else {
canvas.style.opacity = '1';
overlay.classList.remove('visible');
message.classList.remove('visible');
content.classList.remove('visible');
}
});
html {
height: 2000vh;
}
body {
background: #000;
height: 2000vh;
margin: 0;
}
canvas {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
max-height: 100vh;
max-width: 100vw;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 1s ease-in-out;
pointer-events: none;
background: #000;
}
.message {
color: #fff;
font-size: 2rem;
transform: scale(0);
transition: transform 1s ease-in-out;
}
.overlay.visible {
opacity: 1;
}
.message.visible {
transform: scale(1);
}
content {
position: absolute;
background: #000;
color: #fff;
padding-top: 20px;
text-align: center;
margin-top: 100vh;
opacity: 0;
transition: opacity 1s ease-in-out;
}
.content.visible {
opacity: 1;
}
<canvas width="300" height="200" class="ultragear-scrolling"></canvas>
<div class="overlay">
<h1 class="message">Ultra Gear </h1>
</div>
<div class="content">
<h2>Additional Content</h2>
<img src="https://gi.esmplus.com/BLADER01/Hyuk/27gs95qe/27gs95qe_01.png" alt="Additional Image" />
</div>
I've introduced 2 functions to be used on the canvas: makeFixed
and makeAbsolute
which attempt to change position
while maintaining visual position.
Also introduced a .below-canvas
element which accordingly is located below the canvas.
Now the trick is when to make the switch between aboslute
and fixed
and another trick is how calculate the maxScrollTop
etc. The result is not perfect but it's a start.
const html = document.documentElement;
const canvas = document.querySelector('.ultragear-scrolling');
const context = canvas.getContext('2d');
const overlay = document.querySelector('.overlay');
const message = document.querySelector('.message');
const content = document.querySelector('.content');
const below = document.querySelector('.below-canvas');
const currentFrame = index => (
`https://gi.esmplus.com/BLADER01/Hyuk/oled-moon/${index.toString().padStart(0, '1')}.jpg`
);
// frameCount= Number of image
const frameCount = 294;
// set canvas dimensions
canvas.height = 770;
canvas.width = 1158;
const images = [];
// Preload images
const preloadImages = () => {
for (let i = 1; i <= frameCount; i++) {
const img = new Image();
img.src = currentFrame(i);
images.push(img);
}
};
preloadImages();
// Create, load, and draw the image
const img = new Image();
img.src = currentFrame(1);
img.onload = function() {
drawCentered(img);
}
const updateImage = index => {
// Clear the previous image
context.clearRect(0, 0, canvas.width, canvas.height);
// Draw the new image
drawCentered(images[index]);
};
function drawCentered(img) {
const offsetX = (canvas.width - img.width) / 2;
const offsetY = (canvas.height - img.height) / 2;
context.drawImage(img, offsetX, offsetY);
}
let animationFrameId;
addEventListener('scroll', () => {
cancelAnimationFrame(animationFrameId);
const scrollTop = html.scrollTop;
const maxScrollTop = html.scrollHeight - window.innerHeight - 50;
const scrollFraction = scrollTop / maxScrollTop;
const frameIndex = Math.min(
frameCount - 1,
Math.floor(scrollFraction * frameCount)
);
animationFrameId = requestAnimationFrame(() => updateImage(frameIndex));
if (frameIndex >= frameCount - 1) {
canvas.style.transition = 'opacity 1s ease-in-out';
canvas.style.opacity = '0.5';
overlay.classList.add('visible');
message.classList.add('visible');
// Delay showing the content to ensure the message is fully visible
setTimeout(() => {
content.classList.add('visible');
makeAbsolute(canvas)
makeAbsolute(overlay)
}, 1000); // Adjust delay as needed
} else {
canvas.style.opacity = '0.5';
overlay.classList.remove('visible');
message.classList.remove('visible');
content.classList.remove('visible');
makeFixed(canvas)
makeFixed(overlay)
}
});
function makeAbsolute(fixedElement) {
const rect = fixedElement.getBoundingClientRect();
fixedElement.style.position = 'absolute';
fixedElement.style.transform = 'translate(0,0)';
const newTop = rect.top;
const newLeft = rect.left;
fixedElement.style.top = `${window.pageYOffset + rect.top}px`;
fixedElement.style.left = `${window.pageXOffset + rect.left}px`;
below.style.position = 'absolute';
below.style.top = `${window.pageYOffset + rect.top + rect.height}px`;
}
function makeFixed(abosluteElement) {
abosluteElement.style.position = 'fixed';
abosluteElement.style.transform = 'translate(-50%,-50%)';
abosluteElement.style.top = `50%`;
abosluteElement.style.left = `50%`;
below.style.position = 'fixed';
below.style.top = `100%`;
}
html {
height: 2000px;
margin: 0;
padding: 0;
}
body {
background: #000;
height: 2000px;
margin: 0;
padding: 0;
}
canvas {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
max-height: 100vh;
max-width: 100vw;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 1s ease-in-out;
pointer-events: none;
background: #000;
}
.message {
color: #fff;
font-size: 2rem;
transform: scale(0);
transition: transform 1s ease-in-out;
}
.overlay.visible {
opacity: 1;
}
.message.visible {
transform: scale(1);
}
content {
position: absolute;
background: #000;
color: #fff;
padding-top: 20px;
text-align: center;
margin-top: 100vh;
opacity: 0;
transition: opacity 1s ease-in-out;
}
.content.visible {
opacity: 1;
}
.below-canvas {
position: fixed;
top: 100%;
}
<canvas width="300" height="200" class="ultragear-scrolling"></canvas>
<div class="overlay">
<h1 class="message">Ultra Gear </h1>
</div>
<div class="content">
<h2>Additional Content</h2>
<img src="https://gi.esmplus.com/BLADER01/Hyuk/27gs95qe/27gs95qe_01.png" alt="Additional Image" />
</div>
<div class="below-canvas">
<h2>hello hellloo</h2>
</div>