介绍
《你的性情主导色》是往年网易云音乐前端团队开发的一款测试用户主导色的 H5 利用,上线后反应很好,刷爆了微博和朋友圈。
我的项目的次要开发者 imyzf
发表了一篇文章《官网揭秘!你的色彩是这样算进去的》,解释了一些动效和最初主导色的计算方面的问题。但因为波及到了具体的业务,所以作者没有开源出源码,然而热心的作者给了很多的提醒。我就是依据这些提醒,揭秘了我比拟感兴趣的局部。
在线 Demo
因为始终没有在生产环境中应用 Vue3.0
和vite
,所以源码局部我应用了 Vue3.0
+vite
实现。
页面预加载
答题类页面与个别的 H5 页面的不同之处在于,用户的操作门路是确定的,即每个页面的下一页路由是固定的,所以在 router 层面做了优化,提前预加载了下一个页面
因为流动页面应用了大量的视频和动效等,所以想在用户浏览选择题目标过程中把下一页的页面渲染结束,这样切换到下一页面的时候会很晦涩,体验很好。
最后就想着怎么利用 vue-router
实现页面的预加载。然而搞了一圈发现,都是基于 webpack
或者 vite
的懒加载,提前加载了一些资源,并不会提前渲染出页面。
起初通过看 vue-router
文档,才找到了灵感,利用命名视图,同时展现 2 个视图,应用 css
暗藏下一页,这时候尽管不显示,然而页面曾经渲染进去了。
通过批改 router-view
的 name
属性,实现页面的切换。也就是说,其实我的路由是没有变动的。
// 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
}
}
];
看下面的代码,Index1
、Index2
和 Session1
等其实就是每一页的组件了,通过批改 currentViewName
和nextViewName
就能够达到页面切换的目标。
最终的成果是下图这样的,下一页曾经提前渲染进去:
翻页动效
作者提醒说应用 canvas
实现了页面切换时候的幕布拉动成果,次要使用了最外围的 canvas API
是 bezierCurveTo
。
通过查问得悉,bezierCurveTo
须要 3 个 点用来绘制三次贝赛尔曲线,在线体验
看下图,想要实现拉动动画,P1 P2 P3
的 X
轴坐标须要继续变动,而后绘制曲线,就可能实现拉动的成果了。
我这里应用了比拟轻量的 JavaScript
动画库animejs
,用来管制下面几个点的继续挪动。3 个动画成果别离挪动了P1 P2 P3
的X
轴坐标,再配合曲线的绘制,就达到了根本的拉动幕布成果。
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 和提出倡议。