


Let's Make One of Those Fancy Scrolling Animations Used on Apple Product Pages
Apple's product pages are known for their smooth animations. For example, when you scroll down the page, the product may slide into view, the MacBook will turn on, the iPhone will rotate, all of which showcase the hardware, demonstrate the software, and tell interactive stories about how the product is used.
Just check out this video of the iPad Pro mobile web experience:
Many of the effects you see there are not created using only HTML and CSS. So, you would ask? Well, this may be a little hard to figure out. Even using the browser's DevTools doesn't always reveal the answer, as it's usually impossible to see<canvas></canvas>
Content outside the element.
Let's dig into one of the effects and see how it's made so that you can recreate some of these magical effects in our own projects. Specifically, let's copy dynamic light effects from AirPods Pro product pages and hero images.
Basic concepts
The idea is to create an animation, just like a series of fast-sequential images. You know, just like flipping a book! No complex WebGL scenarios or advanced JavaScript libraries are required.
By synchronizing each frame with the user's scrolling position, we can play the animation as the user scrolls the page down (or up).
Start with tags and styles
HTML and CSS for this effect are very simple, because magic happens in<canvas></canvas>
Inside an element, we control it using JavaScript by assigning it ID.
In CSS, we set the height of the document to 100vh and our content height to 5 times higher than 100vh to give us the necessary scroll length to make it work. We also match the background color of the document with the background color of the image. The last thing we have to do is position<canvas></canvas>
element, center it and limit the maximum width and height so that it does not exceed the size of the viewport.
<code>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%); }</code>
Now we can scroll down the page (even if the content does not exceed the viewport height), our<canvas></canvas>
Still at the top of the viewport. That's all we need for HTML and CSS.
Let's continue loading the image.
Get the correct image
Since we will use the image sequence (again, like flipping a book), we will assume that the file names are numbered in ascending order in succession (i.e., 0001.jpg, 0002.jpg, 0003.jpg, etc.) in the same directory.
We will write a function that returns the path and number of the desired image file based on the user's scroll position.
<code>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` )</code>
Since the image number is an integer, we need to convert it to a string and use padStart(4, '0')
to precede our index until we reach the four digits to match our filename. For example, passing 1 to this function will return 0001.
This gives us a way to process image paths. This is<canvas></canvas>
The first image in the sequence drawn on the element:
As you can see, the first image is on the page. At this point, it is just a static file. What we want is to update it based on the user's scrolling position. We not only want to load one image file, but then replace it by loading another image file. We hope to be<canvas></canvas>
Draw the image on top and update the drawing with the next image in the sequence (but we'll discuss it later).
We have created a function that generates the path of the image file based on the numbers we pass in, so what we need to do now is track the user's scroll position and determine the image frame corresponding to that scroll position.
Connect the image to the user's scrolling progress
To understand which number we need to pass (and therefore which image we want to load) in the sequence, we need to calculate the user's scrolling progress. We will create an event listener to track the event and process some mathematical calculations to calculate the image to be loaded.
We need to know:
- Where the scroll starts and ends
- User scrolling progress (i.e. percentage of page scrolling down by the user)
- Image corresponding to user scrolling progress
We will use scrollTop
to get the vertical scroll position of the element, in this case the top of the document. This will be used as the starting value. We will get the end (or maximum) value by subtracting the window height from the document scroll height. From there, we divide scrollTop
value by the maximum value the user can scroll down, which will give us the user's scrolling progress.
We then need to convert that scrolling progress to an index number corresponding to our sequence of image numbers in order to return the correct image for that position. We can do this by multiplying the number of progress by the number of frames (number of images) we have. We will round that number down using Math.floor()
and wrap it in Math.min()
and our maximum number of frames so that it never exceeds the total number of frames.
<code>window.addEventListener('scroll', () => { const scrollTop = html.scrollTop; const maxScrollTop = html.scrollHeight - window.innerHeight; const scrollFraction = scrollTop / maxScrollTop; const frameIndex = Math.min( frameCount - 1, Math.floor(scrollFraction * frameCount) ); });</code>
Update with the correct image<canvas></canvas>
We now know which image to draw when the user's scrolling progress changes. This is<canvas></canvas>
where the magic works.<canvas></canvas>
With lots of cool features to build everything from games and animations to design model generators and in between!
One of these features is a method called requestAnimationFrame
, which works with the browser to update<canvas></canvas>
, if we are using a direct image file instead of<canvas></canvas>
, we can't do this. This is my choice<canvas></canvas>
Method instead of e.g. img
element or background image<div> The reason. <code>requestAnimationFrame
will match the browser refresh rate and enable hardware acceleration by rendering with the device's video card or integrated graphics card using WebGL. In other words, we will get a very smooth transition between frames – no image flickering!
Let's call this function in our scroll event listener to exchange images when the user scrolls up or down the page. requestAnimationFrame
takes a callback parameter, so we will pass a function to update the image source and<canvas></canvas>
Draw a new image on:
<code>requestAnimationFrame(() => updateImage(frameIndex 1))</code>
We increment frameIndex
by 1, because while the image sequence starts at 0001.jpg, our scrolling progress calculations actually start at 0. This ensures that the two values are always aligned.
The callback function we pass to the update image looks like this:
<code>const updateImage = index => { img.src = currentFrame(index); context.drawImage(img, 0, 0); }</code>
We pass frameIndex
to the function. This sets the image source using the next image in the sequence, which is drawn in our<canvas></canvas>
on the element.
Preloading images are better
Technically, we're done now. But please, we can do better! For example, fast scrolling can cause a little lag between image frames. This is because each new image sends a new network request, requiring a new download.
We should try to preload the image with new network requests. This way, each frame has been downloaded, making the transition faster and the animation smoother!
All we have to do is loop through the entire sequence of images and load them:
<code>const frameCount = 148; const preloadImages = () => { for (let i = 1; i </code>
Demo!
Quick description of performance
While this effect is very cool, it is also a lot of images. To be precise, it is 148 pieces.
No matter how we optimize images, or how fast the CDNs are to provide them, loading hundreds of images will always cause the page to bloat. Suppose we use this feature multiple times on the same page. We may get the following performance statistics:
This may be fine for users with high-speed internet connections and low data caps, but we can't say that for users without this convenience. It's a difficult issue to balance, but we have to pay attention to everyone's experience – and how our decisions affect them.
There are a few things we can do to help achieve this balance, including:
<code>- 加载单个后备图像而不是整个图像序列- 为某些设备创建使用较小图像文件的序列- 允许用户启用序列,也许可以使用启动和停止序列的按钮苹果采用第一种方法。如果您在连接到缓慢的3G 连接的移动设备上加载AirPods Pro 页面,那么,性能统计数据开始看起来好多了:是的,它仍然是一个繁重的页面。但它比我们根本没有任何性能考虑的情况下要轻得多。这就是苹果能够将如此多的复杂序列放到单个页面上的方式。 #### 进一步阅读如果您有兴趣了解这些图像序列是如何生成的,一个好的起点是AirBnB 的Lottie 库。文档将引导您完成使用After Effects 生成动画的基础知识,同时提供一种在项目中轻松包含它们的方法。</code>
The above is the detailed content of Let's Make One of Those Fancy Scrolling Animations Used on Apple Product Pages. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



This is the 3rd post in a small series we did on form accessibility. If you missed the second post, check out "Managing User Focus with :focus-visible". In

The CSS box-shadow and outline properties gained theme.json support in WordPress 6.1. Let's look at a few examples of how it works in real themes, and what options we have to apply these styles to WordPress blocks and elements.

The Svelte transition API provides a way to animate components when they enter or leave the document, including custom Svelte transitions.

If you’ve recently started working with GraphQL, or reviewed its pros and cons, you’ve no doubt heard things like “GraphQL doesn’t support caching” or

In this article we will be diving into the world of scrollbars. I know, it doesn’t sound too glamorous, but trust me, a well-designed page goes hand-in-hand

How much time do you spend designing the content presentation for your websites? When you write a new blog post or create a new page, are you thinking about

With the recent climb of Bitcoin’s price over 20k $USD, and to it recently breaking 30k, I thought it’s worth taking a deep dive back into creating Ethereum

npm commands run various tasks for you, either as a one-off or a continuously running process for things like starting a server or compiling code.
