preface

Two days ago, when browsing apple 16 “marketing page, I found several interesting interactions, thinking that although I am a poor person, but knowledge is unlimited, so I studied a wave.

This article is mainly about interactive effects, so there will be a lot of GIFs in the article, you’d better connect to the wireless to see, I put the link to the sample code at the bottom of the article, if you need to help yourself.

 

Two effects

Flip effect

One is the effect of slowly opening the screen, in the process of opening the screen, the computer picture is fixed in the screen, until the end of opening or closing the end of the computer picture with the scroll bar.

Zoom pictures

It starts out as a full-screen image, turns into another image as it scrolls, and then shrinks to a point in the middle of the screen. In this process, the image is fixed in the center of the screen, and when it shrinks to a certain point, the image scrolls along with the scroll bar.

 

Front knowledge

Before we start coding, there are a few things we need to know about the rest of the code.

Viscous positioningsticky

It can be simply considered as a mixture of relative positioning and fixed positioning. Elements are relative positioning before they span a specified range, and then fixed positioning.

The fixed relative offset of a sticky element is relative to its nearest ancestor element with a scroll box. If none of the ancestors can scroll, then the offset of the element is calculated relative to the viewport.

A case in point

The HTML structure is as follows:

<body>
  <h1>I was Sticky's first demo</h1>
  <nav>
    <h3>A navigation</h3>
    <h3>Navigation B</h3>
    <h3>Navigation C</h3>
  </nav>
  <article>
    <p>.</p>
    <p>.</p>
    // ...
  </article>
</body>  
Copy the code

The style is as follows:

nav {
  display: table;
  width: 100%;
  position: sticky;
  top: 0;
}
Copy the code

In code, the nav element is stickily positioned according to the body. The element is relative positioned until the viewport is scrolled to the top of the element less than 0px, meaning it scrolls along the document. After that, the element is fixed at 0px from the top.

The principle of

The sticky principle we can take a look at the teacher Zhang Xinxu’s in-depth understanding of the calculation rules of sticky position, you can first simply take a look at the teacher to explain the sticky with this picture:

Where

  • When the whole blue area is in the red area, the sticky element has no sticky effect (see Figure 1).

  • When slowly sliding up, the blue box exceeds the red rolling element, then the sticky element will slide down in the blue box to achieve the sticky effect (see fig.2 and 3).

  • When the blue box marks the red box, the sticky element is in the blue box, so it is directly carried away by a wave without the sticky effect (see Figure 3).

In fact, we can clearly know why the sticky element’s height cannot be equal to its father’s height, because if it is equal, the sticky positioning element has no space to achieve the sticky effect, which is equivalent to failure.

The above principles refer to the teacher Zhang Xinxu’s in-depth understanding of the calculation rules of sticky positioning. The article explains the concept of flow box and viscous constrained rectangle, as well as the specific code structure and CSS implementation, you can check the original text.

Common examples

In business, we may encounter such a scenario: the data in the list needs to be displayed according to the time, and the time needs to be fixed at the top when scrolling, so we can use sticky to solve the problem:

The HTML structure is as follows:

<body>
  <h1>Fixed time demo</h1>
  <div className={styles.wrapper}>
    <section>
      <h4>On May 20</h4>
      <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
      </ul>
    </section>
    <section>
      <h4>On May 19</h4>
      <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
      </ul>
    </section>
    // ...
</body>
Copy the code

The style is as follows:

body {
  margin: 0px;
  padding: 100px;
  height: 2000px;
}

h4 {
  margin: 2em 0 0;
  background-color: # 333;
  color: #fff;
  padding: 10px;
  top: 0;
  z-index: 1;
  position: sticky;
}
Copy the code

In the code above, each of these sections is a

, then set

to the sticky location to achieve the above effect.

Pay attention to the point

Of course, when using sticky, we need to pay attention to several points:

  • The parent element cannot have any overflow Settings other than overflow: Visible or it will have no sticky effect. If sticky doesn’t work, check to see if the parent element has overflow:hidden set, remove it.

  • You must specify one of the four values top, bottom, left, or right, otherwise the position will be relative.

  • The height of the parent element cannot be lower than the height of the sticky element (see above)

  • The sticky element only works in its parent element (see above)

Can I use sticky compatibility Can I use sticky compatibility

It is completely invalid under IE, if your project needs to consider IE, you will need to use Fixed compatibility.

 

Scroll the parallaxbackground-attachment

What is rolling parallax? Take a look at this example:

Parallax Scrolling means to create a three-dimensional Scrolling effect by allowing multiple backgrounds to move at different speeds.

The effect in the figure above, we only need one line of CSS to achieve, do not need to write complex JS code, directly set background-attachment: fixed to complete.

htmlstructure

<body>
  <section className={` ${styles.gImg} ${styles.gImg1} `} >IMG1</section>
  <section className={` ${styles.gImg} ${styles.gImg2} `} >IMG2</section>
  <section className={` ${styles.gImg} ${styles.gImg3} `} >IMG3</section>
</body>
Copy the code

Style code

section {
  height: 100vh;
}

.gImg {
  background-attachment: fixed;
  background-size: cover;
  background-position: center center;
  width: 100%;
}

.gImg1 {
  background-image: url(@/assets/mac1.jpg);
}

.gImg2 {
  background-image: url(@/assets/mac2.jpg);
}

.gImg3 {
  background-image: url(@/assets/mac4.jpg);
}
Copy the code

We can basically implement the second animation by scrolling the parallax CSS.

For the rolling parallax, you can refer to this article rolling parallax? CSS is written in great detail.

 

Canvasdrawing

In fact, we can also use canvas to draw the second animation. We can draw two pictures on a canvas and display the proportion of the two pictures in the canvas according to the scrolling distance.

Drawing can be done through the drawImage method provided by canvas. This method provides many ways to draw images on canvas.

For example, what we need to achieve is drawn as follows:

In fact, we need to intercept the top half of the first chapter of the picture, the bottom half of the next picture, and then splice oJBK, look at the parameter explanation diagram:

Here we need to pass in seven parameters to achieve the desired effect:

ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
Copy the code

You can refer to the drawImage() MDN document.

The idea is to draw the first image as the base image, and then we can draw the second image to cover part of the first image, so that we can achieve the effect mentioned above. Assuming the original width and height of our image is 2048*1024, the canvas size is 544*341, and the offset is offsetTop when scrolling, we can write the following code:

function drawImage() {
  context.drawImage(img1, 0.0.2048.1024.0.0.544.341);
  context.drawImage(img2, 0, rolling offset distance *1024 / 341.2048.1024.0, rolling offset distance,544.341);
}
Copy the code

Use CTX. DrawImage (image, dx, dy, dWidth, dHeight). Use React Hooks to create image preview. You can refer to this article to draw pictures on canvas several methods, written in detail.

 

transformIn thematrix

CSS3 uses transform to transform elements. These include: displacement, rotation, offset, scale. Transform can be used to translate/rotate/skew/scale to control element transformation, can also be used to control element transformation matrix.

Here’s an example:

/ / code
transform: matrix(1.5.0.0.1.5.0.190.5);
/ / code 2
transform: scale(1.5.1.5) translate(0.190.5)
Copy the code

The above two lines of code mean the same thing, and we will use this property for our second animation.

If you want to know more about this property, you can refer to the matrix in CSS3 Transform without studying mathematics in college

 

Open lu

Initialize the project

To do a good job, he must sharpen his tools. The author used React Hooks to complete these two animation effects, and used UMI to quickly initialize a project. For specific initialization steps, please refer to dVA Theory to Practice written by the author — To help you clear the blind spots of DVA knowledge, which describes in detail how to use scaffolding to quickly build a project.

After the completion of the construction, the author will be mentioned before the examples are put here, convenient for everyone to click into view.

Flip effect

Flip effect is actually very simple, you would never think, Apple marketing page is how to do?

It takes 120 images, and it plots the image that should be displayed at this scrolling position according to the scrolling distance, yes, you heard that right. I also thought it should be CSS3 to control the Angle of the cover to achieve the flip effect, is I think too much, hahaha.

Train of thought

Then our implementation is simple, we just need to do the following:

  • The first step is to define a constant from covered to fully openedHow far to rollTo do that, we define theta here as theta400px.
  • We need to know when to flip or close the cover, which allows the image to animate while it’s in the middle of the screen.
// Start animation scrollTop
// $('#imgWrapper') a container to put images in
startOpen = $('#imgWrapper').offset().top - (window.innerHeight / 2 - $('#imgWrapper').height() / 2);
Copy the code
  • When flip or close the cover, we need to fix the computer in the viewport, wait until fully open or closed, then let the start with the scroll bar, here we can useposition: sticky.

htmlstructure

<body>
 // ...
 <div className={styles.stickyContainer}>
   <div className={styles.stickyWrapper}>
     <div id="imgWrapper" className={styles.imgWrapper}>
       <img
          src={require(` @ /assets/ ${asset}.jpg`)}
          alt="Picture 1"
       />
     </div>
   </div>
 </div>
 // ...
</body>
Copy the code

The dynamic introduction of pictures can be accomplished by require(picture path). In the code above, we only need to calculate the name of the picture to be displayed for the corresponding scrolling distance.

Style code

.stickyContainer {
  height: 150vh;
}

.stickyWrapper {
  height: 100vh;
  position: sticky;
  top: 100px;
}

.imgWrapper {
  width: 100vh;
  height: 521px;
  margin: 0 auto;
}

.imgWrapper img {
  width: 100%;
}
Copy the code

The next step is to figure out which image to display first while scrolling, as we mentioned above: 120 images, 400px scrolling distance to complete the animation.

First, after reloading, we can get the scroll value startOpen at the top of the document, so we can get the following code:

useEffect(() = > {
  // Bind events
  window.addEventListener('scroll', scrollEvent, false);

  // The scrolling distance to start the animation
  // startOpen
  startOpen = $('#imgWrapper').offset().top - (window.innerHeight / 2 - $('#imgWrapper').height() / 2);

  return () = >{
    window.removeEventListener('scroll', scrollEvent, false); }} []);// Scroll events
const scrollEvent = () = > {
  // Real-time scrollTop
  const scrollTop = $('html').scrollTop();
  let newAsset = ' '

  if (scrollTop > startOpen && scrollTop < startOpen + 400) {
    let offset = Math.floor((scrollTop - startOpen) / 400 * 120);
    
    if (offset < 1) {
      offset = 1;
    } else if (offset > 120) {
      offset = 120;
    }

    if (offset < 10) {
      newAsset = `large_000${offset}`;
    } else if (offset < 100) {
      newAsset = `large_00${offset}`;
    } else {
      newAsset = `large_0${offset}`; }}// determine the boundary value
  / /...
  
  // Set the image URL
  setAsset(newAsset);
};
Copy the code

preview

This flip animation is very simple, 120 pictures to change, real-time rendering corresponding pictures, in fact, no technical content, we can also try to use other methods to achieve a wave.

 

Zoom pictures

Zooming pictures to the screen animation can be realized in two ways, one is scrolling parallax realization, and the other is canvas rendering pictures in real time during scrolling.

Before we begin, let’s take a look at the previous image without enlargement, as follows:

It consists of two images. The image shown on the screen has a distance of 18px from the top of the computer case. When enlarged, the distance between the image and the top of the computer case should be 18 * magnification ratio.

Computer case picture, as follows:

Let’s start with two implementations.

 

Canvasimplementation

Canvas implementation is to draw the picture displayed on the screen by Canvas.

Train of thought

In fact, this animation has two parts, one is the picture coverage, one is the picture reduction.

  • Pictures covered

To do this with Canvas, to do this with Canvas we need to draw two images onto a Canvas using the drawImage method. You just need to figure out how much scale the canvas should draw on the first image and how much scale the canvas should draw on the second image at a specific time by scrolling the distance. You just need to know when to start overlay.

  • Resizing images

We use transform: Matrix to achieve this, where image reduction is scaled based on a point in the center of the screen.

We calculate the corresponding magnification ratio and translate value according to the scrolling distance, as shown in the figure below. Change the parameter value of transform: matrix in real time.

Here we need to calculate the values of several critical points, such as the maximum/small magnification ratio, the maximum/small offset value, the point at which shrinkage begins, etc.

  • When I animate,canvasThe package container should bestickyPositioned in the viewport until the end of the animation,canvasThe container will scroll along with the scroll bar.
Some important values

Here we need to know a few values:

  • Defined constant
// The width of the image displayed on canvas
const CANVAS_WIDTH = 544;

// The height of the image displayed on the canvas
const CANVAS_HEIGHT = 341;

// The length of the animation
const ZOOM_SCROLL_RANGE = 400;

// The actual width of the image displayed on the canvas
const IMG_NATURAL_WIDTH = 2048;

// The actual height of the image displayed on the canvas
const IMG_NATURAL_HEIGHT = 1024;
Copy the code
  • Amplification ratio (curScale) formatrixscale

The minimum magnification ratio is 1, that is, self.

The maximum magnification ratio is the height of the screen divided by the ratio of the picture displayed on the screen. Here, the author sets the width and height of the picture drawn on the canvas as 544 * 341.

const CANVAS_WIDTH = 544;
const CANVAS_HEIGHT = 341;

const scaleRadio = window.innerHeight / CANVAS_HEIGHT;
Copy the code

Therefore, the amplification ratio should be between 1 and scaleRadio.

  • Offset distance (translate) formatrixThe offset value

The maximum offset is the distance from the top of the viewport when curScale is 1. Our zoom is always based on the point in the center of the screen, so we can easily get:

// The largest translate
let StartScale = 0;
StartScale = window.innerHeight / 2 - $('#img-wrapper').height() / 2;
Copy the code

The minimum offset distance should be the distance between the wrapped element and the top of the viewport when curScale is scaleRadio. In this case, we need to use the value of top = 18px from the video image to the computer shell as mentioned above. Since the image is enlarged, the minimum offset distance should be:

miniTranslate = - 18 * scaleRadio
Copy the code

Therefore, the offset distance should be between miniTranslate and StartScale.

  • Starting point for scaling operation (NewStartScale)

In fact, it is very simple. We need to start zooming when the picture in chapter 2 completely covers the picture in the first one. This value can be calculated by adding the height of one screen to the top value of the Canvas wrapped element from the top document.

let NewStartScale = 0;

NewStartScale = $('#section-sticky-hero').offset().top + window.innerHeight;
Copy the code
The core code

The core code is the calculation of scrolling:

const scrollEvent = () = > {
  // The current scrollTop
  const scrollTop = $('html').scrollTop();
  // The magnification ratio defaults to maximum
  let curScale = scaleRadio;
  // The offset distance is minimum by default
  let translate = -scaleRadio * 18;

  // StartScale: the maximum offset distance
  // NewStartScale: the starting point for scaling
  // Return if no
  if(! NewStartScale || ! StartScale)return;

  // Calculate the current curScale
  // (scaleRadio - 1)/ZOOM_SCROLL_RANGE) : how many zooms per 1px
  // scrollTop + scaleRadio * 18 - NewStartScale: how much scrolling is currently done
  curScale = scaleRadio - ((scaleRadio - 1) / ZOOM_SCROLL_RANGE) * (scrollTop + scaleRadio * 18 - NewStartScale);

  // boundary value processing
  if (curScale > scaleRadio) {
    curScale = scaleRadio;
  } else if (curScale < 1) {
    curScale = 1;
  }

  // Calculate the current translate
  // Start from scaleRadio * 18
  // all = scaleRadio * 18 + StartScale
  // add as you slide
  translate = -scaleRadio * 18 + ((scrollTop + scaleRadio * 18 - NewStartScale) / ZOOM_SCROLL_RANGE * (scaleRadio * 18 + StartScale));

  // boundary value processing
  if (translate > StartScale) {
    translate = StartScale;
  } else if (translate < -scaleRadio * 18) {
    translate = - scaleRadio * 18;
  }

  // Use canvas for drawing
  if (image1 && image2) {
    // In the image overlay phase
    // curScale is still the largest ratio
    if (curScale === scaleRadio) {
      drawImage({
        img1: image1,
        img2: image2,
        secTop: CANVAS_HEIGHT * (scrollTop + 18 * scaleRadio - NewStartScale) / window.innerHeight,
      });
    } else {
      // If it is not the maximum ratio, the image is already covered
      // Display the second chapter directly
      drawImage({
        img1: image1,
        img2: image2,
        secTop: 0}); }}// Set the style
  $('#img-wrapper').css({
    transform: `matrix(${curScale}, 0, 0, ${curScale}, 0, ${translate}) `}); };Copy the code

The HTML structure is as follows:

<body>/ /... Other content<div id="section-sticky-hero" className={styles.stickyContainer}>
    <div className={styles.componentContainer}>
      <div className={styles.imgWrapper} id="img-wrapper">
        <canvas ref={canvasRef} id="canvas" className={styles.canvas}></canvas>
      </div>
    </div>
  </div>/ /... Other content</body>
Copy the code

Space is limited, the author only lists the code and HTML structure of the scroll event, other code, such as drawImage method, if you are interested, you can refer to the source code.

Preview the renderings

 

Rolling parallax implementation

Previously we also talked about the principle of scrolling parallax, with the background-attachment: fixed property, the second animation is almost half realized.

Implementation approach

Compared with the above canvas drawing, in fact, this step of picture coverage is different. Other steps are basically similar, including the calculation of boundary values.

  • Pictures covered

Here we need to set both images as background images, and we need to cover the second image with a computer case image.

When the first image fills the screen, add background-attachment: fixed property to both images. Do not add this property at the beginning, otherwise it will become the following effect:

  • Resizing images

Instead of using transform: matrix to zoom in and out, we use background-position and background-size to zoom in/zoom out and offset the image.

  • Everything elseCanvasThe implementation principle is much the same.
The core code

The scrolling logic code is as follows:

const CANVAS_WIDTH = 544;
const CANVAS_HEIGHT = 341;

const WRAPPER_WIDTH = 694;
const WRAPPER_HEIGHT = 408;

const ZOOM_SCROLL_RANGE = 400;

// scalaRadio
// The maximum magnification of the image
const scaleRadio = window.innerHeight / CANVAS_HEIGHT;

const scrollEvent = () = > {
  const scrollTop = $('html').scrollTop();
  let curScale = scaleRadio;
  let translate = -scaleRadio * 18;

  if(! imgFixFixed || ! StartScale)return;

  ImgFixFixed The distance to the top of the document is imgFixFixed
  // The height of the image in chapter 1 is 100vh, i.e. the height of a screen
  ImgFixFixed + window.innerheight = imgFixFixed + window.innerheight
  if (scrollTop > imgFixFixed && scrollTop < imgFixFixed + window.innerHeight) {
    // Set the fixed property
    setFixImg(true);
  } else {
    setFixImg(false);
  }

  // Suppose we zoom by 400
  // Then we can calculate the scale per 1px
  // Then multiply that ratio times the distance of the roll
  curScale = scaleRadio - ((scaleRadio - 1) / ZOOM_SCROLL_RANGE) * (scrollTop - imgFixFixed - window.innerHeight);

  // curScale boundary value processing
  // ...

  // Start from scaleRadio * 18
  // all = scaleRadio * 18 + StartScale
  // add as you slide
  translate = -scaleRadio * 18 + ((scrollTop - imgFixFixed - window.innerHeight) / ZOOM_SCROLL_RANGE * (scaleRadio * 18 + StartScale));

  // Translate boundary value processing
  // ...

  // Set the image's CSS style
  // Zoom the image based on the center point
  $('#g-img2').css({
    "width": curScale * CANVAS_WIDTH,
    "height": curScale * CANVAS_HEIGHT,
    "margin-top": `${translate + 18 * curScale}px`}); $('#img-wrapper').css({
    "width": scaleRadio * WRAPPER_WIDTH,
    "height": scaleRadio * WRAPPER_HEIGHT,
    "background-size": `${curScale * WRAPPER_WIDTH}px ${curScale * WRAPPER_HEIGHT}px`."background-position": `center ${translate}px`}); };Copy the code

The HTML structure is as follows:

<body>/ /... Other content<section id="g-img" className={` ${styles.gImg} ${styles.gImg1} ${fixImg ? styles.fixed :"'} `} >IMG1</section>

  <div className={styles.stickyContainer}>
    <div className={styles.componentContainer}>
      <div className={styles.imgWrapper} id="img-wrapper">
        <section id="g-img2" className={` ${styles.gImg} ${styles.gImg2} ${fixImg ? styles.fixed :"'} `} >IMG2</section>
      </div>
    </div>
  </div>/ /... Other content</body>
Copy the code
Preview the renderings

 

conclusion

Today, I talked about the animation of two Apple marketing pages. There is no difficulty in the article. It is mainly the application of several basic knowledge points. Sticky positioning, scrolling parallax, Canvas drawing, matrix property use and so on, I hope to help you.

Honestly, I want a thumbs up!

 

The resources

  • Deeply understand the calculation rules of sticky position
  • Rolling parallax? CSS is no problem
  • Use React Hooks to implement graph-like preview plugin
  • Several ways to draw pictures on canvas
  • You need to understand the matrix in CSS3 Transform without learning mathematics in college
  • Dva theory to Practice – to help you clear the knowledge blind spots of DVA

 

The sample code

  • Apple marketing page animation example code