Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div id="root"></div>
              
            
!

CSS

              
                html {
  height: 100vh;
}

body {
  background: #000;
  color: #86868b;
}

canvas {
  position: sticky;
  top: 50%;
  transform: translateY(-50%);
  max-width: 100vw;
  max-height: 100vh;
  margin: auto;
  display: block;
}

.content {
  position: relative;
  padding: 20px;

  @media (min-width: 768px) {
    padding: 20px 60px;
  }

  h1 {
    color: #f5f5fd;
    font-size: 42px;
  }

  p {
    line-height: 1.5;
  }
}

              
            
!

JS

              
                import React, {
  useState,
  useEffect,
  useRef
} from "https://cdn.skypack.dev/react@17.0.1";
import { render } from "https://cdn.skypack.dev/react-dom@17.0.1";

function getCurrentFrame(index) {
  return `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 ImageCanvas = ({ scrollHeight, numFrames, width, height }) => {
  const canvasRef = useRef(null);
  const [images, setImages] = useState([]);
  const [frameIndex, setFrameIndex] = useState(0);

  function preloadImages() {
    for (let i = 1; i <= numFrames; i++) {
      const img = new Image();
      const imgSrc = getCurrentFrame(i);
      img.src = imgSrc;
      setImages((prevImages) => [...prevImages, img]);
    }
  }

  const handleScroll = () => {
    const scrollFraction = window.scrollY / (scrollHeight - window.innerHeight);
    const index = Math.min(
      numFrames - 1,
      Math.ceil(scrollFraction * numFrames)
    );

    if (index <= 0 || index > numFrames) {
      return;
    }

    setFrameIndex(index);
  };

  const renderCanvas = () => {
    const context = canvasRef.current.getContext("2d");
    context.canvas.width = width;
    context.canvas.height = height;
  };

  useEffect(() => {
    preloadImages();
    renderCanvas();
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  useEffect(() => {
    if (!canvasRef.current || images.length < 1) {
      return;
    }

    const context = canvasRef.current.getContext("2d");
    let requestId;

    const render = () => {
      context.drawImage(images[frameIndex], 0, 0);
      requestId = requestAnimationFrame(render);
    };

    render();

    return () => cancelAnimationFrame(requestId);
  }, [frameIndex, images]);

  return (
    <div style={{ height: scrollHeight }}>
      <canvas ref={canvasRef} />
    </div>
  );
};

const App = () => (
  <main>
    <ImageCanvas
      scrollHeight={4000}
      width={1158}
      height={770}
      numFrames={147}
    />
    <div className="content">
      <h1>Cras tincidunt lobortis</h1>
      <p>
        Gravida quis blandit turpis cursus in. Tellus in metus vulputate eu
        scelerisque felis. Sed vulputate mi sit amet mauris. Iaculis urna id
        volutpat lacus laoreet. Duis tristique sollicitudin nibh sit. Dui
        vivamus arcu felis bibendum ut tristique. Morbi tincidunt augue interdum
        velit. Diam phasellus vestibulum lorem sed risus ultricies tristique.
        Varius duis at consectetur lorem donec. Massa sed elementum tempus
        egestas sed. Tortor condimentum lacinia quis vel eros donec ac odio
        tempor. Amet mattis vulputate enim nulla aliquet porttitor lacus luctus.
        Nunc aliquet bibendum enim facilisis. Volutpat sed cras ornare arcu dui
        vivamus arcu. Faucibus a pellentesque sit amet. Senectus et netus et
        malesuada fames ac turpis egestas integer. Bibendum at varius vel
        pharetra vel. Non enim praesent elementum facilisis leo. Pharetra diam
        sit amet nisl suscipit. Egestas erat imperdiet sed euismod.
      </p>
      <p>
        Tortor aliquam nulla facilisi cras fermentum odio eu feugiat.
        Suspendisse ultrices gravida dictum fusce ut placerat. Semper risus in
        hendrerit gravida rutrum quisque non tellus orci. Sed adipiscing diam
        donec adipiscing tristique risus nec feugiat. Ultrices tincidunt arcu
        non sodales neque sodales ut etiam sit. Vitae proin sagittis nisl
        rhoncus mattis rhoncus urna. Ut etiam sit amet nisl purus in mollis
        nunc. Suspendisse faucibus interdum posuere lorem. Mauris commodo quis
        imperdiet massa. Pretium vulputate sapien nec sagittis aliquam. Lorem
        ipsum dolor sit amet consectetur adipiscing elit pellentesque. Auctor eu
        augue ut lectus arcu bibendum at varius.
      </p>
      <p>
        Commodo viverra maecenas accumsan lacus. Diam vel quam elementum
        pulvinar etiam non. Turpis egestas integer eget aliquet nibh. Cras
        tincidunt lobortis feugiat vivamus at augue. Ornare aenean euismod
        elementum nisi quis. Aliquet enim tortor at auctor urna nunc id cursus.
        Etiam dignissim diam quis enim lobortis scelerisque fermentum dui. Et
        odio pellentesque diam volutpat commodo. Euismod nisi porta lorem mollis
        aliquam ut porttitor leo a. Sollicitudin aliquam ultrices sagittis orci.
        Netus et malesuada fames ac turpis egestas maecenas pharetra convallis.
        Facilisi cras fermentum odio eu feugiat pretium nibh ipsum. Leo integer
        malesuada nunc vel risus commodo. Dapibus ultrices in iaculis nunc sed.
        Tellus pellentesque eu tincidunt tortor aliquam. Volutpat maecenas
        volutpat blandit aliquam etiam erat velit scelerisque.
      </p>
    </div>
  </main>
);

render(<App />, document.getElementById("root"));

              
            
!
999px

Console