介绍
《你的性情主导色》是往年网易云音乐前端团队开发的一款测试用户主导色的 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 和提出倡议。