Talk about a few interesting interactive animations on Apple ’s marketing page

Posted May 27, 202016 min read

Foreword

When browsing the Apple 16-inch Marketing Page two days ago, I found a few interesting interactions. A brutal force, but knowledge is boundless, so he studied a wave.

The article mainly talks about the interactive effects, so there will be a lot of gif pictures in the article. It is best to connect to the wireless and read it again. I have placed the sample code link at the bottom of the article. I need to pick it up.

Two effects

Flip effect

One is the effect of the screen opening slowly. During the opening of the screen, the computer picture is fixed on the screen until the opening or closing is completed, and then the computer picture is scrolled. Scrolling.

Zoom picture

At the beginning, it was a full-screen picture, which gradually became another picture during the scrolling process, and then the picture was gradually reduced with the center of the screen as the reference point. During the process of zooming out, this picture is fixed on the screen In the center, when zoomed out to a certain value, the picture scrolls with the scroll bar.

Pre-knowledge

Before writing the code again, we need to understand a few knowledge points to be used in the next code.

Sticky positioning sticky

It can be simply considered as a mixture of relative positioning relative and fixed positioning fixed, the elements are relative positioned before crossing the specified range, and then fixed positioned.

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

one example

The following code, the structure of html is as follows:

<body>
  <h1> I am sticky's first demo </h1>
  <nav>
    <h3> Navigation A </h3>
    <h3> Navigation B </h3>
    <h3> Navigation C </h3>
  </nav>
  <article>
    <p> ... </p>
    <p> ... </p>
    //...
  </article>
</body>

The style is as follows:

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

In the code, the nav element will be sticky positioned according to body. Before the viewport scrolls to the element whose top distance is less than 0px, the element is relatively positioned, which means it will scroll with the document. After that, the element will be fixed at a distance of 0px from the top.

Principle

The principle of sticky, you can take a look at Zhang Xinxu s In-depth understanding of the calculation rule of position sticky sticky positioning , it can be simple first Take a look at this picture used by the teacher when explaining sticky:

Picture taken from Zhang Xinxu's deep understanding of position sticky

Where <nav> is the sticky element, the blue box area is the sticky element of sticky, which is used to carry the sticky element, and the red area is the relative scrollable element of<nav>.

  • When the entire blue area is in the red area, the sticky element has no sticky effect(see Figure 1);
  • When sliding slowly upwards, the blue box exceeds the red rolling element, then the sticky element will slide down in the blue box to achieve the sticky effect(Figures 2 and 3);
  • When the blue box is drawn out of the red box, because the sticky element is in the blue box, it is directly taken away by a wave without a sticky effect(see Figure 3).

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

The above principles refer to Zhang Xinxu's In-depth understanding of the calculation rule of position sticky sticky positioning , which is explained in the article The concept explanation of the flow box and sticky constraint rectangle, as well as the specific code structure and css implementation, you can view the original text.

Common examples

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

The specific html structure is as follows:

<body>
  <h1> Time fixed demo </h1>
  <div className = {styles.wrapper}>
    <section>
      <h4> May 20 </h4>
      <ul>
        <li> 1 </li>
        <li> 2 </li>
        <li> 3 </li>
        <li> 4 </li>
      </ul>
    </section>
    <section>
      <h4> May 19 </h4>
      <ul>
        <li> 1 </li>
        <li> 2 </li>
        <li> 3 </li>
      </ul>
    </section>
    //...
</body>

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;
}

The code is as above, each of which is a <section>, and then set sticky positioning for<h4>, so that the above effect can be achieved.

be careful

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

  • The parent element cannot have any overflow setting other than overflow:visible, otherwise there is no sticky effect. If the sticky you set has no effect, you can see if the parent elements have set overflow:hidden, just remove it.
  • One of the four values top, bottom, left, and right must be specified, otherwise it will only be in relative positioning.
  • The height of the parent element cannot be lower than the height of the sticky element(refer to the principle explanation above)
  • The sticky element only takes effect within its parent element(refer to the principle explanation above)

Another thing I have to mention is compatibility. We can check the compatibility of sticky on the official website of Can I use .

It is completely obsolete under IE. If your project needs to consider IE, you need to use fixed for compatibility.

Rolling parallax background-attachment

What is the rolling parallax, look at the following example to understand:

Parallax Scrolling(Parallax Scrolling) refers to let the multi-layer background move at different speeds to form a three-dimensional motion effect, bringing a very good visual experience.

The effect in the picture above, we only need a line of css to achieve it, without writing complicated js code, directly set background-attachment:fixed to complete.

html structure

<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>

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);
}

By scrolling the parallax css we can basically achieve the second animation.

For the explanation of rolling parallax, you can refer to this article Rolling Parallax? CSS is not a problem , written in great detail.

Canvas drawing

In fact, we can also use the canvas drawing for 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.

You can use the drawImage method provided by canvas to draw pictures. This method provides multiple ways to draw images on Canvas.

For example, we need to achieve the following picture:

In fact, we need to intercept the first half of the image in the first chapter, the lower half of the next image, and then splice it into ojbk, see the parameter explanation diagram:

Here we need to pass in 7 parameters to achieve the effect we need:

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

The meaning of the specific parameters will not be elaborated here. You can refer to drawImage() MDN document .

The idea is roughly to first draw the first picture as a base picture, and then we will cover part of the first picture by drawing the second picture, so as to achieve the aforementioned effect. Suppose the original width and height of our picture is 2048 * 1024, the size of the canvas is 544 * 341, and the offset distance when scrolling is offsetTop, so that we can write the following code:

function drawImage() {
  context.drawImage(img1, 0, 0, 2048, 1024, 0, 0, 544, 341);
  context.drawImage(img2, 0, scroll offset distance * 1024/341, 2048, 1024, 0, scroll offset distance, 544, 341);
}

I have used ctx.drawImage(image, dx, dy, dWidth, dHeight) before. I can refer to the [Preview plugin for realizing graphite-like images using React Hooks][ https://juejin.im/post/5e9bf299f265da47ee3f6c31 ), this time uses 7 parameters, you can refer to this article Several methods for drawing pictures on canvas Is very detailed.

matrix in transform

Use transform in CSS3 to transform elements. It includes:displacement, rotation, offset, scaling. transform can use translate/rotate/skew/scale to control element transformation, or matrix to control element transformation.

for example:

//code one
transform:matrix(1.5, 0, 0, 1.5, 0, 190.5);
//code two
transform:scale(1,5, 1.5) translate(0, 190.5)

The meaning of the above two lines of code is the same, and we will use this property when we do the second animation later.

If you want to learn more about this property, you can refer to: Undergraduates who have n t studied mathematics should also understand the matrix in CSS3 transform

Open

Initialize the project

If a worker wants to do his job well, he must first sharpen his weapon. The author uses react Hooks to complete these two animation effects, and uses umi to quickly initialize a project. The specific initialization steps can refer to the author s [Dva theory to practice-help you clear the blind spots of dva knowledge] https://juejin.im/post/5e6e08b16fb9a07cc50f24dc#heading-32 ), which details how to quickly build a project using scaffolding.

After the construction is completed, the author will put all the examples mentioned earlier here, so that everyone can click to view.

Flip effect

The flip effect is actually very simple, you absolutely can't think of it, how does the Apple marketing page do it?

It uses 120 pictures, and draws the corresponding picture to be displayed at this scroll position according to the scroll distance Yes, you have heard it correctly. I used to think that it should be css3 to control the angle of the cover to achieve the flip effect, I think too much, hahaha.

Ideas

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

  • First of all, we must define a constant that stipulates how much distance to scroll from cover to fully open. ** It is defined as 400px here.

  • We need to know when to start flipping or closing the lid. This allows the picture to start animating when the picture is in the middle of the screen.

    //scrollTop to start animation
    //$('# imgWrapper') is the container for the pictures, there are below the html structure
    startOpen = $('# imgWrapper'). offset(). top-(window.innerHeight/2-$('# imgWrapper'). height()/2);

  • When flipping or closing the lid, we need to fix the computer in the viewport, wait until it is fully opened or closed, and then let it scroll with the scroll bar. Here we can use position:sticky

html structure

<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>

Among them, the dynamic introduction of pictures can be done through require(picture path). As in the above code, we only need to calculate the name of the picture to be displayed corresponding to the scroll 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%;
}

The next step is to calculate which image is to be displayed during the scrolling process. We mentioned above:120 images, complete the animation within the scrolling distance of 400px.

First of all, we can get it after loading, we can get the scroll value startOpen from the top of the document to start the animation, so we can get the following code:

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

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

  return() => {
    window.removeEventListener('scroll', scrollEvent, false);
  }
}, []);

//scroll event
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}`;
    }
  }

  //boundary value judgment
  //....

  //Set the picture url
  setAsset(newAsset);
};

Preview effect

This flip animation is very simple, 120 pictures are exchanged, and the corresponding pictures are rendered in real time. In fact, there is no technical content. You can also try other methods to achieve a wave.

Zoom picture

We can achieve the animation of zooming the picture to the screen in two ways, one is scrolling parallax and the other is canvas rendering the picture in real time during the scrolling process.

Before we start, let's take a look at the previous picture without enlargement, as follows:

It consists of two pictures. The upper distance between the picture and the computer case is 18px. When zoomed in, the upper margin of the picture and the computer case should be 18 * magnification ratio .

Computer case picture, as follows:

Next, we will introduce two implementation methods.

Canvas implementation

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

Ideas

In fact, this animation consists of two parts, one is picture overlay, and the other is picture zooming out.

  • Picture overlay

Use Canvas to solve, using Canvas to achieve we need to use the drawImage method to draw two pictures on a canvas. It only needs to calculate the proportion of the first picture and the proportion of the second picture that the canvas should draw at a certain time through the distance of scrolling. Just need to know when to start the picture overlay.

  • Picture zoom out

We use transform:matrix to achieve, in which the picture is zoomed based on the point in the center of the screen.

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

Here we need to calculate the value of several critical points, such as the maximum/small enlargement ratio, the maximum/small offset value, the point where it starts to shrink, etc.

  • When animating, the canvas package container should be sticky positioned in the viewport. Until the animation ends, the canvas package container will not scroll with the scroll bar.
Some important values

Here we need to know several values:

  • Defined constants

    //The width of the picture displayed by canvas
    const CANVAS_WIDTH = 544;

    //Picture height displayed by canvas
    const CANVAS_HEIGHT = 341;

    //The distance the animation lasts
    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 picture displayed on the canvas
    const IMG_NATURAL_HEIGHT = 1024;

  • Magnification ratio(curScale), used for matrix scale value

The smallest magnification ratio is 1, which is itself.

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

const CANVAS_WIDTH = 544;
const CANVAS_HEIGHT = 341;

const scaleRadio = window.innerHeight/CANVAS_HEIGHT;

So the range of the magnification ratio should be between 1 ~ scaleRadio.

  • Offset distance(translate), used for the offset value of matrix

The maximum offset distance should be the distance of the wrapped element from the top of the viewport when curScale is 1, and our zoom has always been based on the point in the center of the screen to zoom in/out, so it can be very simple inferred:

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

The minimum offset distance should be the distance from the top of the viewport when the curScale is scaleRadio. At this time, we need to use the aforementioned picture from the screen to the top of the computer shell top = 18px This value, because the picture is enlarged, so the minimum offset distance should be:

miniTranslate =-18 * scaleRadio

So the offset distance should be between miniTranslate ~ StartScale.

  • The starting point for starting the zoom operation(NewStartScale)

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

let NewStartScale = 0;

NewStartScale = $('# section-sticky-hero'). Offset(). Top + window.innerHeight;
Core code

The core code is the calculation when scrolling:

const scrollEvent =() => {
  //current scrollTop
  const scrollTop = $('html'). scrollTop();
  //The enlargement ratio defaults to the maximum
  let curScale = scaleRadio;
  //Offset distance defaults to minimum
  let translate = -scaleRadio * 18;

  //StartScale:the maximum offset distance
  //NewStartScale:the starting point to start the zoom operation
  //return if not
  if(! NewStartScale ||! StartScale) return;

  //Calculate the current curScale
  //(scaleRadio-1)/ZOOM_SCROLL_RANGE):how much to enlarge every 1px
  //scrollTop + scaleRadio * 18-NewStartScale:how much is currently scrolled
  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
  //from scaleRadio * 18
  //all = scaleRadio * 18 + StartScale
  //Continue to add during the sliding process
  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 to draw pictures
  if(image1 && image2) {
    //During the picture overlay stage
    //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 largest ratio, the picture has been overwritten
      //Directly display the second chapter
      drawImage({
        img1:image1,
        img2:image2,
        secTop:0,
      });
    }
  }

  //Set the style
  $('# img-wrapper'). css({
    transform:`matrix(${curScale}, 0, 0, ${curScale}, 0, ${translate})`,
  });
};

The structure of html 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>

The space is limited. The author only lists the code of the scroll event and the html structure. Other codes, such as the drawImage method, if you are interested, you can refer to the source code.

Preview renderings

Rolling parallax implementation

Earlier we also talked about the principle of scrolling parallax. With this background-attachment:fixed attribute, the second animation is basically half done.

Implementation ideas

Compared with the above drawing of canvas, it is actually a different step in the picture overlay. Others are basically similar, including the calculation of the boundary value.

  • Picture overlay

Here we need to set both pictures as background pictures, and at the same time we need to put the computer shell picture on the second picture.

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

  • Picture zoom out

Here we don't use transform:matrix to do this enlargement and reduction, we use background-position and background-size to reduce/enlarge and offset the image.

  • Everything else is similar to the principle of Canvas implementation.
Core code

The rolling 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 picture
const scaleRadio = window.innerHeight/CANVAS_HEIGHT;

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

  if(! imgFixFixed ||! StartScale) return;

  //The distance of the first picture from the top of the document is imgFixFixed
  //The height of the first chapter picture is 100vh, which is the height of one screen
  //So the scrollTop of the second chapter picture is imgFixFixed + window.innerHeight
  if(scrollTop> imgFixFixed && scrollTop <imgFixFixed + window.innerHeight) {
    //Set the fixed attribute
    setFixImg(true);
  } else {
    setFixImg(false);
  }

  //Suppose our zoom distance is 400
  //Then we can calculate the scale of every 1px zoom
  //Then multiply this ratio by the scrolling distance
  curScale = scaleRadio-((scaleRadio-1)/ZOOM_SCROLL_RANGE) *(scrollTop-imgFixFixed-window.innerHeight);

  //curScale boundary value processing
  //...

  //from scaleRadio * 18
  //all = scaleRadio * 18 + StartScale
  //Continue to add during the sliding process
  translate = -scaleRadio * 18 +((scrollTop-imgFixFixed-window.innerHeight)/ZOOM_SCROLL_RANGE *(scaleRadio * 18 + StartScale));

  //translate boundary value processing
  //...

  //Set the css style of the picture
  //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`,
  });
};

The structure of html 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>
Preview renderings

to sum up

Today I talked about the animation of two Apple marketing pages. The article is not difficult, mainly on the use of several basic knowledge points. sticky positioning, scrolling parallax, Canvas drawing, use of the matrix property, etc., I hope to help everyone.

Do not hide, I want to like it!

References

Sample code