1

I'm trying to reverse engineer an animation from this codepen

my HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="script.js"></script>
    <canvas id="hero-lightpass"></canvas>  
</body>
</html>

My CSS

html {
height: 100vh;
}

body {
background: #000;
height: 500vh;
}

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

h1 {
    color: white;
}

My JS:

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

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

    
    // Preloading images to drastically improve performance
    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 frameCount = 148; // There 148 images for that animation-sequence to load
    const images = [];

    const preloadImages = () => {
        for (let i = 1; i < frameCount; i++) {
            images[i] = new Image(); // This is functionally equivalent to document.createElement('img').
            images[i].src = currentFrame(i);
        }
    };

    preloadImages();


    // Draw the first image
    const img = new Image();
    img.src = currentFrame(1);
    img.onload = function(){
        context.drawImage(img, 0, 0);
    }


    // Scroll interactions
    const html = document.getElementsByTagName('html');
    
    window.addEventListener('scroll', () => {  
        const scrollTop = html[0].scrollTop;
        // console.log('scrollTop: ', scrollTop);
        // console.log('html.scrollHeight: ', html[0].scrollHeight);
        // console.log('window.innerHeight: ', window.innerHeight);
        const maxScrollTop = html[0].scrollHeight - window.innerHeight;
        const scrollFraction = scrollTop / maxScrollTop;
        const frameIndex = Math.min(
            frameCount - 1,
            Math.floor(scrollFraction * frameCount)
        );
        // console.log('FrameIndex', frameIndex);

        requestAnimationFrame(() => context.drawImage(images[frameIndex + 1], 0, 0));

    });

but it doesn't work on my computer for some reason. It gives me the error message

"Uncaught TypeError TypeError: Cannot read properties of null (reading 'getContext') at (/home/philocalyst/Desktop/test/script.js:3:32)" in my terminal and

"Uncaught TypeError: canvas is null file:///home/philocalyst/Desktop/test/script.js:3" in my console

the code is an exact copy of the codepen, but with the linking of the css and javascript. I can't figure out what I did wrong.

I tried linking the two together and reading the code and messages.

j08691
  • 204,283
  • 31
  • 260
  • 272
  • 2
    try moving the `script` declaration at the end of the `body` tag. alternatively, before running any of your javascript check [whether the document is ready](https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState). – Bagus Tesa Jul 17 '23 at 02:35
  • @BagusTesa I was about to say the same thing – Parking Master Jul 17 '23 at 02:49
  • Does this answer your question? [Why does jQuery or a DOM method such as getElementById not find the element?](https://stackoverflow.com/questions/14028959/why-does-jquery-or-a-dom-method-such-as-getelementbyid-not-find-the-element) – jabaa Jul 17 '23 at 02:56
  • simply add `defer` attribute to your ` – Mister Jojo Jul 17 '23 at 02:59

1 Answers1

1

Like @BagusTesa said in the comments, the reason it is not working is clearly because the canvas selector in your script returns null.

This is because the DOM has not fully loaded yet and the script needs to wait until it's ready:

window.addEventListener("load", function() {
  // Code goes here
});

Or you need to put the script's source or code before the closing <body> tag:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <canvas id="hero-lightpass"></canvas>
  <script src="script.js"></script>
</body>
</html>

In the new code, you can probably tell that the <script> tag is put after the canvas element so when the script runs, it knows that the canvas is there because it is run after that element has loaded.

Also refer to this source (MDN)

Parking Master
  • 551
  • 1
  • 4
  • 20