介绍

《你的性情主导色》是往年网易云音乐前端团队开发的一款测试用户主导色的 H5 利用,上线后反应很好,刷爆了微博和朋友圈。

我的项目的次要开发者 imyzf 发表了一篇文章《官网揭秘!你的色彩是这样算进去的》,解释了一些动效和最初主导色的计算方面的问题。但因为波及到了具体的业务,所以作者没有开源出源码,然而热心的作者给了很多的提醒。我就是依据这些提醒,揭秘了我比拟感兴趣的局部。

在线 Demo

因为始终没有在生产环境中应用Vue3.0vite,所以源码局部我应用了 Vue3.0+vite实现。

页面预加载

答题类页面与个别的 H5 页面的不同之处在于,用户的操作门路是确定的,即每个页面的下一页路由是固定的,所以在 router 层面做了优化,提前预加载了下一个页面

因为流动页面应用了大量的视频和动效等,所以想在用户浏览选择题目标过程中把下一页的页面渲染结束,这样切换到下一页面的时候会很晦涩,体验很好。

最后就想着怎么利用 vue-router 实现页面的预加载。然而搞了一圈发现,都是基于webpack或者vite的懒加载,提前加载了一些资源,并不会提前渲染出页面。

起初通过看vue-router文档,才找到了灵感,利用命名视图,同时展现 2 个视图,应用css暗藏下一页,这时候尽管不显示,然而页面曾经渲染进去了。

通过批改router-viewname 属性,实现页面的切换。也就是说,其实我的路由是没有变动的。

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

看下面的代码,Index1Index2Session1等其实就是每一页的组件了,通过批改currentViewNamenextViewName就能够达到页面切换的目标。

最终的成果是下图这样的,下一页曾经提前渲染进去:

翻页动效

作者提醒说应用canvas实现了页面切换时候的幕布拉动成果,次要使用了最外围的 canvas APIbezierCurveTo

通过查问得悉,bezierCurveTo 须要 3 个 点用来绘制三次贝赛尔曲线,在线体验

看下图,想要实现拉动动画,P1 P2 P3X轴坐标须要继续变动,而后绘制曲线,就可能实现拉动的成果了。

我这里应用了比拟轻量的JavaScript 动画库animejs,用来管制下面几个点的继续挪动。3 个动画成果别离挪动了P1 P2 P3X轴坐标 ,再配合曲线的绘制,就达到了根本的拉动幕布成果。

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

最终实现的成果是这样的:

这个动效因为每一页都须要应用,所以思考实现一个通用的全局组件。

思考到应用的时候个别组件须要写到vue 模板下面,很不不便,所以最好通过一个全局函数间接显示这段动效,相似于showAnimation();

首先须要实现一个独立的组件,因为想笼罩掉页面的所有信息,所以应用了 Vue3.0 最新提供的teleport 组件:

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

而后须要把组件通过 Vue 插件的形式注册到全局属性,因为我想应用 Composition API ,所以最终决定应用 provide + inject 的形式注册和应用全局 property。个别的状况下应用app.config.globalProperties就能够了,然而这种配合Composition API写起来会比拟麻烦,不举荐。

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

最初,因为翻页动效和路由都在一起应用,就持续封装了个useNext函数,这样在个别的view组件应用的话,就非常简单了,同时做了翻页动效和翻页的操作:

nextPage();

到这里我能够夸夸Composition API了,十分的简略和不便,通过这个全局通用组件的封装,我彻底喜爱上了这种形式。

云层动效

这部分是我感觉最乏味的,以前用three.js实现过一个 3D 照片墙,然而这个云层动效真的牛,也是最难破解的,还好被我搞定了,下篇具体阐明破解的过程。

源码

最初放上源码,感兴趣的同学能够看一下,欢送 Star 和提出倡议。