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:
scroll
Calculated in the event listening callbackscrollIndex
The value of therequestAnimationFrame
To deal withcurrentIndex
andscrollIndex
The value of the- Pass the increment or decrement process
Canvas
thedrawImage
Method 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.