头图

introduce

"Your Personality Dominant Color" is an H5 application developed by the NetEase Cloud Music front-end team this year to test user-dominant colors. The response was very good after it went live, and it blasted Weibo and Moments.

The main developer of the project imyzf published an article "Official Secrets! This is how your colors are calculated", which explains some of the calculations of motion effects and the final dominant color. But because of the specific business involved, the author did not open source the source code, but the enthusiastic author gave a lot of tips. Based on these tips, I revealed the part that I am more interested in.

Online Demo

Vue3.0 and vite have not been used in the production environment, Vue3.0 + vite for the source code part.

Page preload

The difference between the answering page and the general H5 page is that the user's operation path is determined, that is, the next page route of each page is fixed, so the router level is optimized, and the next page is preloaded in advance

Since the activity page uses a lot of videos and motion effects, I want to render the next page when the user is reading the selected topic, so that the switch to the next page will be smooth and the experience is good.

At first I thought about how to use vue-router complete the page preload. However, I found out that they were all based on webpack or vite . Some resources were loaded in advance, and the page was not rendered in advance.

Later, I found inspiration by looking at the vue-router document. I used named views to show 2 views at the same time. Use css hide the next page. Although it is not displayed at this time, the page has been rendered.

By modifying router-view of name property, to complete the handover of the page. In other words, my routing has not changed.

// App.vue
<template>
  <router-view :name="currentViewName"></router-view>
  <router-view :name="nextViewName"></router-view>
</template>

// 注意 ,这里使用两个 viewName 完成了页面的跳转,next 的页面被预加载
const currentViewName = computed(() => store.getters.currentViewName);
const nextViewName = computed(() => store.getters.nextViewName);

// router的定义部分
const routes = [
  {
    path: '/',
    components: {
      default: Index1,
      index2: Index2,
      session1: Session1,
      session2: Session2,
      session5: Session5
    }
  }
];

Looking at the above code, Index1 , Index2 and Session1 are actually components of each page. By modifying currentViewName and nextViewName , the purpose of page switching can be achieved.

The final effect is like the following picture, the next page has been rendered in advance:

Page turning animation

The author prompts that the use of canvas achieve the effect of screen switching when the page is switched, mainly using the core canvas API is bezierCurveTo .

Through the query, we know that bezierCurveTo needs 3 points to draw the cubic curve, 1611495c82acc3 online experience

P1 P2 P3 at the picture below, if you want to achieve a pulling animation, the X axis coordinates of 0611495c82acf6 need to be continuously changed, and then draw a curve to achieve the pulling effect.

I used the relatively lightweight JavaScript animation library animejs to control the continuous movement of the above points. The three animation effects respectively move the P1 P2 P3 axis coordinates of X , and then cooperate with the drawing of the curve to achieve the basic pulling curtain effect.

  const heights = [0, 0.5 * pageHeight, pageHeight];
  points = {
    p1: {
      x: pageWidth,
      y: heights[0]
    },
    p2: {
      x: pageWidth,
      y: heights[1]
    },
    p3: {
      x: pageWidth,
      y: heights[2]
    },
    p4: {
      x: pageWidth,
      y: heights[2]
    },
    p5: {
      x: pageWidth,
      y: heights[0]
    }
  };

  // P1点的变化
  anime({
    targets: points.p1,
    x: 0,
    easing: 'easeInQuart',
    delay: 50,
    duration: 500
  });

  // P2点的变化
  anime({
    targets: points.p2,
    x: 0,
    easing: 'easeInSine',
    duration: 500
  });

  anime({
    targets: points.p2,
    y: 0.6 * pageHeight,
    easing: 'easeInSine',
    duration: 500
  });

  // P3点的变化
  anime({
    targets: points.p3,
    x: 0,
    easing: 'easeInQuart',
    delay: 50,
    duration: 500
  });

  // 画曲线
  anime({
    duration: 550,
    update: function () {
      // 清除上一次的绘制
      ctx.clearRect(0, 0, pageWidth, pageHeight);
      ctx.beginPath();
      ctx.moveTo(points.p1.x, points.p1.y);
      // 幕布的上半区域
      ctx.bezierCurveTo(
        points.p1.x,
        points.p1.y,
        points.p2.x,
        points.p2.y - 0.2 * pageHeight,
        points.p2.x,
        points.p2.y
      );
      // 幕布的下半区域
      ctx.bezierCurveTo(
        points.p2.x,
        points.p2.y + 0.2 * pageHeight,
        points.p3.x,
        points.p3.y,
        points.p3.x,
        points.p3.y
      );
      // 已拉动部分的矩形区域
      ctx.lineTo(points.p4.x, points.p4.y);
      ctx.lineTo(points.p5.x, points.p5.y);
      ctx.closePath();
      ctx.fill();
      ctx.strokeStyle = '#f1f1f1';
      ctx.stroke();
    }
  });

The final result is this:

Since this animation needs to be used on every page, consider completing a common global component.

Considering that the general components need to be written to the vue template when using it, it is very inconvenient, so it is best to display this dynamic effect directly through a global function, similar to showAnimation() ;

You first need to complete a separate component, because I wanted to cover all of the information out of the page, so use the Vue3.0 latest available teleport components:

<!-- 这个canvas会被渲染为 app 的子级 -->
  <teleport to="#app">
    <canvas class="mask-canvas" ref="canvas" :class="{ 'mask-canvas-posi': isShow }"></canvas>
  </teleport>

Then I need to register the component to the global property through the Vue plug-in. Because I want to use Composition API , I finally decided to use provide + inject to register and use the global property . Under normal circumstances, it is app.config.globalProperties to use 0611495c82aed9, but this kind of coordination with Composition API will be more troublesome to write, so it is not recommended.

(Mask as any).install = (app: App): void => {
  // Vue3 的 Composition API 建议使用 provide + inject 的方式注册和使用全局 property
  app.provide('mask', Mask);
};

// 使用的时候
const Mask = inject('mask');

Finally, since the page turning animation and routing are used together, we continue to encapsulate the useNext function, so view component. At the same time, the page turning animation and page turning operations are done:

nextPage();

At this point, I can boast about Composition API , which is very simple and convenient. Through the encapsulation of this global common component, I completely like this method.

Cloud dynamics

This part is the most interesting to me. I have used three.js implement a 3D photo wall before, but this cloud dynamic effect is really awesome, and it is also the most difficult to crack. Fortunately, I got it done. The next article will explain the cracking process in detail.

Source code

Finally, put the source code , interested students can take a look, welcome Star and suggestions.


hezhongfeng
260 声望452 粉丝

coder