《你的性格主导色》揭秘

介绍

《你的性格主导色》是今年网易云音乐前端团队开发的一款测试用户主导色的 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就可以达到页面切换的目的。

最终的效果是下图这样的,下一页已经提前渲染出来:

image

翻页动效

作者提示说使用canvas实现了页面切换时候的幕布拉动效果,主要运用了最核心的 canvas APIbezierCurveTo

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

看下图,想要实现拉动动画,P1 P2 P3X轴坐标需要持续变化,然后绘制曲线,就能够实现拉动的效果了。

image

我这里使用了比较轻量的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();
    }
  });

最终完成的效果是这样的:

image

这个动效由于每一页都需要使用,所以考虑完成一个通用的全局组件。

考虑到使用的时候一般组件需要写到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 照片墙,但是这个云层动效真的牛,也是最难破解的,还好被我搞定了,下篇详细说明破解的过程。

image

源码

最后放上源码,感兴趣的同学可以看一下,欢迎 Star 和提出建议。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容