Marcus
Marcus

Reputation: 152

Calculate height of element using JS listener

I've working on an Apple style image sequence scroller from codepen: https://codepen.io/jasprit-singh/pen/LYxzQjB

I want the JS to base the scroll height on a a div that is parent to the canvas, so that I can have content before and after this section without it functioning before it is in the viewport.

I think that const html = document.documentElement; is the issue as it is reading the full contents of <html> rather than the parent div I want to select. However, I don't understand how to change it.

I've tried replacing const html = document.documentElement; with const html = document.getElementById("wrap1"); but this just stop everything working with no console errors.

What am I doing wrong/ What do I need to change?

Thanks in advance!

const html = document.documentElement;

const canvas = document.getElementById("hero-lightpass");
const context = canvas.getContext("2d");

const frameCount = 148;

const currentFrame = index =>
  (`https://www.apple.com/105/media/us/airpods-pro/2019/1299e2f5_9206_4470_b28e_08307a42f19b/anim/sequence/large/01-hero-lightpass/${index.toString().padStart(4, '0')}.jpg`);

const preloadImages = () => {
  for (let i = 1; i < frameCount; i++) {
    const img = new Image();
    img.src = currentFrame(i);
  }
};
const img = new Image();
img.src = currentFrame(1);

canvas.width = 1158;
canvas.height = 770;

img.onload = function() {
  context.drawImage(img, 0, 0);
};
const updateImage = index => {
  img.src = currentFrame(index);
  context.drawImage(img, 0, 0);
}

window.addEventListener('scroll', () => {
  const scrollTop = html.scrollTop;
  const maxScrollTop = html.scrollHeight - window.innerHeight;

  const scrollFraction = scrollTop / maxScrollTop;
  const frameIndex = Math.min(frameCount - 1, Math.ceil(scrollFraction * frameCount));

  requestAnimationFrame(() => updateImage(frameIndex + 1));
});
preloadImages();
#wrap1 {
  position: relative;
  z-index: 1;

  margin: 0;
  padding: 0;

  background: #000;
}

#wrap {
  height: 500vh;
}

canvas {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  max-width: 100vw;
  max-height: 100vh;

  display: flex;
  justify-content: center;
  align-items: center;
}
<div class="wrap1" id="wrap1">
  <div class="wrap" id="wrap">
    <canvas id="hero-lightpass"></canvas>
  </div>
</div>

Upvotes: 4

Views: 164

Answers (1)

Damzaky
Damzaky

Reputation: 10826

From my own perspective, first, we can add overflow-y: auto to the parent. And then we can change the const html = document.documentElement; to the parent (const html = document.getElementById("wrap1");) as you mentioned. And then we need to add the scroll listener to the parent as well: change window.addEventListener('scroll', () => { to html.addEventListener('scroll', () => {. After all that, the image animation should work based on the parent scroll, like this:

const html = document.getElementById("wrap1");

const canvas = document.getElementById("hero-lightpass");
const context = canvas.getContext("2d");

const frameCount = 148;

const currentFrame = index =>
  (`https://www.apple.com/105/media/us/airpods-pro/2019/1299e2f5_9206_4470_b28e_08307a42f19b/anim/sequence/large/01-hero-lightpass/${index.toString().padStart(4, '0')}.jpg`);

const preloadImages = () => {
  for (let i = 1; i < frameCount; i++) {
    const img = new Image();
    img.src = currentFrame(i);
  }
};
const img = new Image();
img.src = currentFrame(1);

canvas.width = 1158;
canvas.height = 770;

img.onload = function() {
  context.drawImage(img, 0, 0);
};
const updateImage = index => {
  img.src = currentFrame(index);
  context.drawImage(img, 0, 0);
}

html.addEventListener('scroll', () => {
  const scrollTop = html.scrollTop;
  const maxScrollTop = html.scrollHeight - window.innerHeight;

  const scrollFraction = scrollTop / maxScrollTop;
  const frameIndex = Math.min(frameCount - 1, Math.ceil(scrollFraction * frameCount));

  requestAnimationFrame(() => updateImage(frameIndex + 1));
});
preloadImages();
#wrap1 {
  z-index: 1;
  margin: 0;
  padding: 0;
  background: #000;
  overflow-y: auto;
  max-height: 100vh;
}

#wrap {
  height: 500vh;
}

#wrap2 {
  position: relative;
}

canvas {
  position: absolute;
  max-width: 100%;
  max-height: 100vh;
  top: 50%;
  pointer-events: none;
  transform: translate(calc(-50% - 20px), -50%); /* add scrollbar width 20px so that it's visible */
  left: 50%;
}

body {
  margin: 0;
}
<div id="wrap2">
  <div class="wrap1" id="wrap1">
    <div class="wrap" id="wrap">
      <canvas id="hero-lightpass"></canvas>
    </div>
  </div>
</div>

Note: I added and removed some CSS too so that it looks similar to that of the snippet.

Upvotes: 2

Related Questions