background

Recently, due to the need of work, I have to develop two relatively cool animation effects. In the early stage, I accumulated too little in this aspect, resulting in a lot of detours in the actual development process. This article is hereby summarized, hoping that all colleagues can learn from the experience (JIA) and (BAN) lessons to avoid detours and get off work as soon as possible.

Without further ado, let’s take a look at some of the coolest animations on Apple’s official website and think about how to implement them.

A detour

When I got the design draft from my design classmate and was told that the first animation I wanted to implement was similar to this, my first reaction was to think of implementing it with a video, by controlling the videocurrentTimeProperty to control the progress of the video, and then monitorscrollEvent, when he triggers I willcurrentTimeAdd or subtract the value of, which turns out to be a very silly operation. Because when the user scrolls up or down quickly on the trackpad,scrollThe response to an event is not triggered consecutively, but the last few responses after throttling, as shown below:For this, I was at a loss, had to Google, found the original monitorscrollThe event is only the first step, the second step is needed based onscrollTop,scrollHeight,clientHeightThen a rolling coefficient is calculated, and the correlation dynamic effect is calculated based on the calculation results.

const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
const scrolled = scrollTop / (scrollHeight - clientHeight);
Copy the code

Applying the formula above, I was full of joy to set currentTime with the calculated SCrolled value, thinking that I could check the point when I came off work, but I found that the animation effect was realized, but when I was rolling fast, there would be the situation of frame dropping and stalling and jumping. As a person who pursues the ultimate user experience, I can’t stand this kind of situation.

But he is not smart enough, only to admit their ignorance, forced to compromise to apple’s official website. Seriously study the effect of apple’s official website in a cubicle is how to realize, after serious debugging research found that apple’s official website was used dozens or even hundreds of images one by one to load, then stitching way to achieve a similar effect, when see the panel that a lot of network request, my heart is rejected, So many performance optimization articles on the web tell us to minimize resource load requests, merge images when you can, and compress code when you can. The Apple iPhone se-Rotation system has been around for a while, and it looks pretty good — > The Apple iPhone se-Rotation system, but I couldn’t get enough of the web requests and decided not to do it.

All those days I was wondering if there was a possibility,It can reduce the number of requests, but it can not drop frames, and it can achieve a silky effect“And it would be nice to look a little bit brainy. Through a Google search for various keywords are can not find the related information, that a few days really reached the point where they doubt their ability, daily and mystery, the thought night, the night in a dream are thinking about how to achieve this, I have been think so, don’t believe that apple’s official website all similar dynamic effect, use this way to achieve, I went through every product page on Apple’s website and finally foundipad-mini”Found the answer.

Since the online code was compressed and there was no source code, I had to look for a needle in a haystack in the code file compressed on Apple’s official website, find his key code implementation, and follow up line by line through breakpoint debugging. Finally, I succeeded in copying the implementation effect of Apple.

The implementation of the Ipad Mini is broken down

1. Build a basic sticky structure

To achieve this scrolling effect on the Ipad Mini, you first need to build a baseposition: stickyLocate the basic structure of the page in the structure.stickyThe height of the node is100vh, and set theoverflow: hidden, here we need to keep the sticky node fixed at the top of the screen and do not need to make it scroll..contentEach of the nodessectionEach node is a content area, and their height is determined by how much scrolling distance they need to take up. In my example, the height of each section child node under the Content node is equal to that of each section child node.timeline-wrapperUnder the.timelineThe three node heights are bound because as the user scrolls, what the user sees is.stickyThe content displacement under the node changes, but the response area of the scroll is.timeline-wrapperNode, which can be implemented.timeline-wrapperHow far do I roll,.contentThe node sets how many offsets so that the interaction matches the visual content seen by the naked eye.

2. Cache video frames

Now that we have this basic collaborative structure, we can start to iterate through the list of video resources when the page loads, get the address of each video resource in the list, and call createVideo to get the video node object.

function createVideo(url) {
  const video = document.createElement("video");

  video.src = url;
  video.muted = true;
  video.playbackRate = 1;
  video.currentTime = 0;
  video.setAttribute("muted"."");
  video.setAttribute("playsinline"."");
  video.setAttribute("type"."video/webm");
  video.setAttribute("preload"."none");
  video.classList.add("video");
  video.style.display = "none";
  window.document.body.appendChild(video);

  return video;
}
Copy the code

Access to video resource information, we can start the video resource frame cache operations, before the frame buffer, we need to compute a video about how much resources we need specific cache frame, such as my case for a video cache about 230 frames, default each cache frame position to false state.

Before caching, we need to start the video playing and immediately perform the video resource buffering, while listening for the CanPlaythrough event, which indicates that enough data has been loaded to play the video and no more buffering until it ends. In this way, when the event is triggered, we can safely rotate the video resource. What I set here is to create a video cache frame every 30ms, and then create a video frame based on the current video resource, and store the created video frame in the framsStore array for future use.

function cacheFrame(videoMetaData) {
  return new Promise((resolve, reject) = > {
    const { url, frameCount } = videoMetaData;
    const video = createVideo(url);
    const framsStore = new Array(frameCount).fill(false);
    let videoWidth = 0;
    let videoHeight = 0;
    let setIn = 0;
    let framsNumber = 0;

    video.play();
    video.addEventListener("loadedmetadata".(res) = > {
      videoWidth = video.videoWidth;
      videoHeight = video.videoHeight;
    });

    video.addEventListener("ended".() = > {
      resolve(framsStore);
    });

    video.addEventListener("waiting".(res) = > {
      clearInterval(setIn);
    });

    video.addEventListener("error".() = > {
      reject([]);
    });

    video.addEventListener("canplaythrough".(res) = > {
      clearInterval(setIn);

      setIn = setInterval(() = > {
        if (framsNumber >= frameCount) clearInterval(setIn);

        framsStore[framsNumber] = createFrame(video, videoWidth, videoHeight);
        framsNumber++;
      }, fps);
    });
  });
}
Copy the code

In video frame caching, we need to draw the video resources of each rotation training to the host object. This API is preferred if the browser supports OffscreenCanvas, otherwise it is relegated to hosting via Canvas Canvas.

function createFrame(video, videoWidth, videoHeight) {
  const canvas = window.OffscreenCanvas
    ? new OffscreenCanvas(videoWidth, videoHeight)
    : document.createElement("canvas");
  const context = canvas.getContext("2d");

  canvas.width = videoWidth;
  canvas.height = videoHeight;
  context.drawImage(video, 0.0, videoWidth, videoHeight);

  return canvas;
}
Copy the code

3. Video frames based on scroll factor rendering cache

When the video frame is cached, the scroll event can be monitored. When the scroll is triggered, the distance of the current scroll and the video frame that should be drawn can be calculated based on the calculated coefficient multiplied by the total number of cached frames.

window.addEventListener("scroll".() = > {
  const scrolled =
    document.documentElement.scrollTop /
    (document.documentElement.scrollHeight -
      document.documentElement.clientHeight);
      
  frames = frames.filter((item) = >item ! = =false);

  const frameIndex = parseInt(frames.length * scrolled) + 1;

  if(frames[frameIndex] ! = =undefined) {
      renderFrame(ctx, frames[frameIndex]);
  }

  document.querySelector(".content").style.transform = `matrix(1, 0, 0, 1, 0, -The ${document.documentElement.scrollTop}) `;
});

function renderFrame(ctx, frame) {
  ctx.clearRect(0.0.1600.1176);
  ctx.drawImage(frame, 0.0);
}
Copy the code

Final full version code -> DEMO code

Preview address online -> Address

Ps: As github server is slow to access, it needs to load video resources, which may take a little time

conclusion

This article through to apple’s official website the mini scroll animation the principle has carried on the exploration, realize the core principle of the animation part manually, there are many twigs distal part not to achieve, apple’s website is also considered in the process of implementation of more performance and compatibility issues, where these details are worth careful scrutiny and learning, Those who are interested can go to the Ipad Mini product page on Apple’s official website for detailed debugging.

Contrast apple’s official website the realization of the existing two rolling animation can be found, when rolling dynamic effect need to be on the first screen will display scenarios, it is recommended to use more photos in a way that, if your website is the first screen, no animation when the user scroll beyond the first screen to animation display of the region, is implemented by means of video will be better.

Finally, if this article has the inadequacy also hope big guys comment area points out. 🤟