Every time I browse the product details page on apple’s official website, I’m sure many young and fat people like me will find the interface and human-computer interaction so smooth and pleasing. As a habitual hobby of my career, WHENEVER Apple releases a new product, I will immediately experience the product introduction page that apple’s front-end development engineers bring to everyone, to feel how Apple tries to restore and experience the ultimate effect of the product through animation.

In this case, the AirPods Pro interface looks something like a movie as the page scrolls along with the mouse. You can visit www.apple.com.cn/airpods-pro to experience this effect.

Project analysis

On the AirPods Pro page, we scroll the mouse, and the page displays an image of the current scroll bar progress as the mouse moves faster or slower. When dragging the scroll bar with the mouse to scroll a certain progress directly, the interface will directly and quickly transition to the screen of the current progress, as if users feel that they are controlling the movie with the mouse, giving people a strong sense of control and experience.

The question is, is the user really manipulating the movie?

Video implementation: The mouse can control the progress of Video. JS can dynamically control the currentTime property (current playing time) of the Video object by monitoring the distance of mouse scrolling. However, this scheme is obviously impractical from the perspective of performance, because the Video must be preloaded first, which requires users to wait for time. Secondly, some browsers will default to record the browsing progress when they visit and leave the page before, that is, the progress of the page scroll bar when they leave. If the user visits the page again, it will jump to the previously browsed progress. Obviously, the video cannot be loaded immediately to the image corresponding to the current progress. Therefore, this scheme is not suitable.

Canvas implementation: Complex animation implementation, Canvas is undoubtedly the first choice. The idea of the scheme is to dynamically display the image of the current progress through the drawImage method of Canvas Canvas, and the scroll bar moves from a certain position to calculate the image index required to display the current progress, and then use the requestAnimationFrame method to display the image index of the beginning and end position for each frame transition. So as to achieve the effect of mouse rolling control.

Code implementation

First we need to prepare the image material. Go to the address above, open the browser console, view the image load and get the image link as shown below:

As you can see, the official website loaded every frame of the picture and the file name in order named. This article selects one of the effects to realize our example. Define the HTML structure of Canvas:

const ScrollPlayer: React.FC = props= > {
    const width = 1458;
    const height = 820;
    const canvasId = 'scroll-player';
    
    return (
        <div className="scroll-player-container">
          <div className="scroll-sequence">
            <div className="image-sequence">
              <div className="canvas-container">
                <canvas id={canvasId} width={width} height={height} style={{background: '#000'}} ></canvas>
              </div>
            </div>
          </div>
        </div>)}Copy the code

Set styles:

.scroll-player-container {
  height: 300vh;
  width: 100%;
  position: relative;
}

.scroll-sequence {
  position: absolute;
  top: 0;
  width: 100%;
  height: 100%;
}

.image-sequence {
  position: sticky;
  top: 0;
  overflow: hidden;
}

.canvas-container {
  position: relative;
  width: 100%;
  height: 100vh;
}
Copy the code

Then place all the images that need to be displayed in each frame into the array as follows:

import numeral from 'numeral';
const imagesLength = 128; // Total number of images
let images: any[] = []; // Image path array collection
const baseUrl = 'https://www.apple.com.cn/105/media/us/airpods-pro/2019/1299e2f5_9206_4470_b28e_08307a42f19b/anim/sequence/large/02-head -bob-turn/';
for(let i = 0; i < imagesLength; i++) {
    images.push(baseUrl + numeral(i).format('0000') + '.jpg')}Copy the code

Canvas initialization and image loading:

let imagesManager: any[] = [];
let canvas: HTMLCanvasElement;
let context: CanvasRenderingContext2D;
// Load the image
function loadImages() {
    const loadNextImage = (src: string) = > {
          const img = new Image();
          // Load the image synchronously, there are performance problems, can be optimized for asynchronous
          img.onload = (e) = > {
                imagesManager.push(img)
                if(imagesManager.length === imagesLength) {
                    // Call back method after all images are loaded
                    imagesLoadComplete()
                }
          }
          img.src = src;
          if(images.length === 0) return;
          loadNextImage(images.shift())
    }
    loadNextImage(images.shift());
}

/ / initialization
function init() :void {
    canvas = document.getElementById(canvasId) as HTMLCanvasElement;
    context = canvas.getContext('2d') as CanvasRenderingContext2D;
    // Add scroll event listener
    document.addEventListener('scroll', handleScroll)
    // Perform loading of all images for each frame
    loadImages();
}

useEffect(() = > {
    init()
    return () = > {
          document.removeEventListener('scroll', handleScroll)
    }
})
Copy the code

RequestAnimationFrame = requestAnimationFrame

let scrollIndex = 0; // The index value of the picture to be displayed
let currentIndex = 0; // The index value of the currently displayed image
let raf = null;
// Image loading completes callback
function imagesLoadComplete() :void{
    console.log('images all loaded! ')
    run()
}
function run() :void {
    raf = window.requestAnimationFrame(draw)
}
Copy the code

Border processing is done using the draw method, while processing the canvas loading image showing the current index. And the bounds are basically determining the direction of the scroll bar to handle the currentIndex increment or decrement.

// 
function draw() :void {
    if(currentIndex <= scrollIndex) {
      drawImages(imagesManager[currentIndex])
      currentIndex + 1 < scrollIndex && currentIndex++;
    } else if(currentIndex >= scrollIndex){
      drawImages(imagesManager[currentIndex])
      currentIndex - 1 > scrollIndex && currentIndex--;
    }
    raf = window.requestAnimationFrame(draw)
}

// Canvas drawing
function drawImages(img: CanvasImageSource) :void 
    context.clearRect(0.0, width, height);
    context.drawImage(img, 0.0);
}
Copy the code

The actual factor controlling dynamic effect is the relationship between currentIndex and scrollIndex, and the scrollIndex corresponding to the current scroll bar progress can be calculated. We then call requestAnimationFrame to increment currentIndex or decrease it to scrollIndex, and this increment or decrement process implements a movie-like effect. So, we need to calculate the value of scrollIndex:

// The mouse scroll event callback calculates the scrollIndex
function handleScroll() {
    const docElement = document.documentElement;
    const scrollHeight = docElement.scrollHeight;
    const clientHeight = docElement.clientHeight;
    const scrollTop = docElement.scrollTop;
    // According to the rolling distance, calculate the scale of the scroll to the map
    scrollIndex = Math.round(scrollTop * imagesLength / (scrollHeight - clientHeight));
}
Copy the code

At this point, we have a simple imitation of a movie animation. As shown in the figure below

conclusion

This article focuses on the apple official website AirPods Pro product introduction page for a simple example of the effect of the movie. Apple’s implementation is more complex and comprehensive, but the principles are similar. The technical points are summarized as follows:

  • scrollCalculated in the event listening callbackscrollIndexThe value of the
  • requestAnimationFrameTo deal withcurrentIndexandscrollIndexThe value of the
  • Pass the increment or decrement processCanvasthedrawImageMethod to load the display picture to achieve the animation effect

If there are any mistakes or omissions in this article, please correct them! Interested students can communicate, discuss and learn from each other in the comments section

Note: all the pictures in the article are from the original website of Apple’s official website. The copyright of the pictures belongs to Apple.