3

又来了!10分钟实现微信 "炸屎"大作战

 2 years ago
source link: https://segmentfault.com/a/1190000040119181
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

又来了!10分钟实现微信 "炸屎"大作战

大家好,我是秋风,近日,微信又发布了新功能(更新到微信8.0.6)。最火热的非"炸屎"功能莫属了,各种群里纷纷玩起了炸屎的功能。

不知道大家是否经历过那样一个时候,小时候过年也会看到邻家小孩干这种恶趣味,没想到微信给它做成了一个线上版本。这个功能发明,连创造产品的本人也进行了调侃。但是能做一个功能让全民玩的开心,也不枉产品的出现的意义了。

之前在微信8.0 更新的时候,我也写过一篇《教你实现微信8.0『炸裂』的礼花表情特效》。之前一篇文章中我是用 canvas 来实现的,上次在文章末有人评论,可以通过 lottie 来实现相似的功能,其实我对这个还是挺感兴趣的,但是一直没有尝试,这一次我就想通过新的方式 lottie 来实现一下这功能。

效果体验:

https://example.qiufeng.blue/...

Github地址:

https://github.com/hua1995116...

任何一个物体都是由更微小的物体构成,因此我们想要实现以上功能,自然也得一步一步地来。我大致将以上功能拆解成了以下四个步骤。里面的每一个都不会太难,可以说前端小白也能轻松实现。

1.丢炸弹

这一步,我们可以用二次函数的知识,来写一个轨迹路径(类似 y = $x^2$ ),然后通过tween.js来做补间动画。

2.炸弹爆炸

利用lottie 来实现动画。

3.粑粑被炸开

利用 css 动画实现

4.所有人震动

利用 css 动画实现

总结

以上我们大致想说的思路,也许你看思路就对其中一些的实现已经轻车熟路,那你可以跳过一部分。有了上面的思路后,那我们就真刀真枪开始实践啦。

1.丢炸弹

image-20210531230319313

我们通过仔细观察其实可以看到,炸弹的运动轨迹其实就是一个抛物线。我们想要实现这个功能,很容易地就可以联想到二次函数。

首先我们先来看看二次函数的定义。

一般地,把形如y=ax²+bx+c(a≠0)(a、b、c是常数)的函数叫做二次函数。

从图像上来表达就是这样的。

image-20210531230619104

很显然这和我们想要的轨迹非常的相似。

因为正常的笛卡尔坐标系都是以竖直向上为 y 正轴,横向向右为 x 正轴。而对于 dom 定位而言,左上方为 (0,0)横向向右为 x 正轴,竖直向下为 y 正轴。只不过将坐标系沿着 x 轴进行了一个翻转。

因此我们只要确定一个二次函数,我们就能得到轨迹。由于二次函数的通项有3个未知数,因此,我们只需要知道3个点就能确定一个二次函数。我们先假定我们的二次函数是这样的。

image-20210531233814669

我们的3个点分别为 (0,H),(H,0),(3H, 3H) 我们通过代入通项可以得出以下公式:

image-20210531234322414

1622476435264-1

1622477842575

因此,我们只需要得到这个炸弹最高点距离"屎"的高度,就能画出整个轨迹。

image-20210531235633501

现在假设我们的炸弹是一个 10px * 10px 的小方块,设置起始点为(300,300)终点为 (0,100) H=100,此时我们得到的二次函数为:

1622477784304

我们就能得到以下轨迹动画:

2021-06-01-00.27.29

而渲染每一帧动画,我们则用了著名的补间动画库Tween.js 补间(动画)是一个概念,允许你以平滑的方式更改对象的属性。你只需告诉它哪些属性要更改,当补间结束运行时它们应该具有哪些最终值,以及这需要多长时间,补间引擎将负责计算从起始点到结束点的值。

var coords = { x: 300 };  // 起始点 为 x = 300
var tween = new TWEEN.Tween(coords)
    .to({ x: 0  }, 1000) // 终点为 x = 0, 并且这个动作将在1秒内完成
  .easing(TWEEN.Easing.Linear.None)    // 匀速

通过以上定义,我们就可以在 onUpdate 中,拿到每次变化的x值,然后通过上面二次函数得到 y,然后对小方块进行更新。

tween.onUpdate(function() {
    var x = coords.x;
    var y = 1/120 * x * x - 11/6 * x + 100;
        box.style.setProperty('transform', 'translate(' + x + 'px, ' + y + 'px)');
})

此时我们完成的效果还是缺了点东西,就像画画一样,我们只给他画了骨骼,我们需要给它包装上色,接下来我们只需要做以下两件事,然后就能看到效果啦~

1.将方块换成炸弹然,炸弹的形状很简单,我们可以通过 ps 将它从图层中抠出来。

2.修改它运动时候的角度。

2021-06-01-23.19.17

本节完整代码:https://github.com/hua1995116...

2.炸弹爆炸

然后再谈谈炸弹爆炸的效果,上面也说了,想换成lottie 来写动画,那么lottie 是什么呢?

Lottie是一个库,可以解析使用AE制作的动画(需要用bodymovin导出为json格式),支持web、ios、android和react native。在web侧,lottie-web库可以解析导出的动画json文件,并将其以svg或者canvas的方式将动画绘制到我们页面中。

然后我去 https://lottiefiles.com/ 找了一个json爆炸的特效文件。

2021-06-01-23.29.33

而它的写法非常简单,只需要引入 lottie ,然后调用 bodymovin.loadAnimation 方法。

<script src="https://cdn.bootcdn.net/ajax/libs/lottie-web/5.7.8/lottie.min.js"></script>
</head>
<body>
<div class="bodymovin"></div>
<script>
    const animation = window.bodymovin.loadAnimation({
        container: document.querySelector('.bodymovin'), // 要包含该动画的dom元素
        renderer: 'svg', // 渲染方式,svg、canvas、html(轻量版仅svg渲染)
        loop: true, // 是否循环播放
        autoplay: true, // 是否自动播放
        path: './bomb.json', // 动画json文件路径
    });
</script>

因此我们只需要在抛物线完成后再立即调用爆炸特效,而tween.js 也给我提供了事件方法onComplete。我们只需要在onComplete回调中,让爆炸动画开始。

tween.onComplete(function () {
  // 写爆炸动画
})

2021-06-02-01.53.54

image-20210602013807913

本节完整代码: https://github.com/hua1995116...

3.粑粑被炸开

3.1形状

同理炸弹用 PS 抠图把 "粑粑" 抠出一个透明的图层,就想这样。(稍微有点毛刺没关系,实际的粑粑也没这么大, 所以不太容易看到毛刺,也可以通过微调给他修复)

feces

.feces {
  position: absolute;
  background-image: url(./feces.png);
  background-size: 100%;
  background-position: center;
  background-repeat: no-repeat;
  width: 80px;
  height: 80px;
  transform-origin: center;
}
// 创建一个粑粑元素
function createfeces(scale = 1) {
  const fece = document.createElement('div');
  fece.className = 'feces';
  // 由于粑粑有大有小,有方向,因此预留了值。
  const symbol = Math.random() > 0.5 ? 1 : -1;
  fece.style.transform = `scale(${scale * 1.1}) rotate(${symbol * 20 * Math.random()}deg)`
  return fece;
}

3.2位置

image-20210602221656176

我们可以看到粑粑是从炸裂的地方飞出来的,飞出来主要是7个粑粑,其中中间的最大,其他的随着离中心粑粑越远而越小,排列的方式是类似于一个圆,但是又不是那么规律。

因此我们可以先通过最简单的方式来实现,就是以一个圆形环绕。一个圆是 360 °,我们只需要给它平均分成6等分就好。我们环绕的一共是6个粑粑,因此,每个之间是60°。

由于我们上面的炸弹是大致是一个 300 * 300的区域,因此我将中心的坐标定为(150,150),然后随机生成一个 70 ~ 230 的x点,就能算出 y 值,在确定第一个点后,根据每个点之间的角度是 60°,就能计算出其余的5个点。

image-20210603002233233

由于用中心点为 (150,150) 为圆心计算比较麻烦,因此我将中心点移到了(0, 0)进行计算,最后再将所有计算出来的点都往 x 轴,y 轴平移 150。

// 计算要生成的多个粑粑的位置
// 传入的参数num为要生成的粑粑的数量
function randomPosition(num) {
  const radius = 80; // 圆半径
  const randomX = Math.random() * radius // 任取0到半径中的任意一个x
  const y = Math.round(Math.sqrt(radius * radius - randomX * randomX)); // 确定一个第一象限在圆上的点
  const radian = Math.atan(y / randomX); // 这个点的弧度值

  const step = Math.PI * 2 / num; // 每坨屎间距的弧度值

  return new Array(num).fill(0).map((item, index) => {
    const r = (index * step + radian)
    // 将弧度为0 - 2 * PI
    const tr = r > Math.PI * 2 ? r - Math.PI * 2 : r < 0 ? r + Math.PI * 2 : r;
    return {
      x: radius * Math.sin(tr),
      y: radius * Math.cos(tr),
    }
  })
            
}

image-20210603002426209

然后我们按照这个思路进行绘制,绘制出 6 个粑粑,再向x轴和y轴分别平移150。

randomPosition(6).map(item => ({ x: item.x + 150, y: item.y + 150 })) // 此处你也定义多于6个

image-20210603002701496

貌似有点那味了,但是所有的都一样大,因此我们需要处理一下,根据距离中心远近来缩放大小,大致写了一个,因为圆的半径为80,每增加 80,就把粑粑的大小变成原来的 2/3。

const dis = Math.sqrt((end.x - 150) * (end.x - 150) + (end.y - 150) * (end.y - 150)); // 由于此时已经平移 150 ,因此需要计算距离中心点的距离
const r = Math.pow(2/3, dis / length); // 要缩放的比例

image-20210603002847549

然而真实场景中,我们摆放位置会更加随机,因此我给每个粑粑的位置增加了一个随机值,并且中心粑粑会更加偏向于左上角,也更加了一定的随机值。

function randomPosition(num) {
...
return new Array(num).fill(0).map((item, index) => {
  const r = (index * step + radian)
  const tr = r > Math.PI * 2 ? r - Math.PI * 2 : r < 0 ? r + Math.PI * 2 : r;
  return {
    // 增加随机值
    x: length * Math.sin(tr) + (Math.random() > 0.5 ? 1 : -1) * 10 * Math.random(),
    y: length * Math.cos(tr) + (Math.random() > 0.5 ? 1 : -1) * 10 * Math.random(),
  }
})
}

image-20210603003823351

3.3角度

最后们只需要点缀一下每个粑粑的角度就可以啦。

function createfeces(scale) {
  const fece = document.createElement('div');
  fece.className = 'feces';
  const symbol = Math.random() > 0.5 ? 1 : -1; // 生成 -20 ~ 20 之间的随机角度
  fece.style.transform = `scale(${scale}) rotate(${symbol * 20 * Math.random()}deg)`
  fece.style.opacity = '0';
  return fece;
}

image-20210603004011365

3.4动画

由于这里和丢炸弹类似,我就不详细展开讲了。需要提一下的是,由于粑粑是先从炸弹位置出来,再缓缓下来,这里我们需要利用两次 Tween 补间动画。

// 一开始的出现时候的动画,从爆炸口冲出来
function initFece(end) {
    ...
  const start = { x: 0, y: 100, z: 0 }; // 爆炸口
  const tween = new TWEEN.Tween(start)
  .to({ ...end, z: 1 }, 100)
  .easing(TWEEN.Easing.Linear.None)
  .onUpdate(function () {
    fece.style.setProperty('top', `${start.y}px`);
    fece.style.setProperty('left', `${start.x}px`);
    fece.style.setProperty('opacity', `${start.z}`);
  })
  .onComplete(function () {
    initDown(start, fece).start(); // 冲出完成,进行下落透明动画
  })
  return tween;
}
// 下落同时变透明动画
function initDown(start, fece) {
  const s = {
    y: start.y,
    o: 1,
  };
  const e = { y: start.y + 80, o: 0 };
  const tween = new TWEEN.Tween(s)
  .to(e, 2000 + 500 * Math.random())
  .easing(TWEEN.Easing.Quadratic.In)
  .onUpdate(function () {
    fece.style.setProperty('top', `${s.y}px`);
    fece.style.setProperty('opacity', `${s.o}`);
  })
  .onComplete(function () {
  })
  return tween;
}

2021-06-03-01.18.57

本节完整代码:https://github.com/hua1995116...

3.5总结

由于这一节比较长,总结一下知识

  • 先利用 1 = x² + y² 圆轨迹的特性,建立初步位置
  • 再通过加入随机值,使得整个分布稍微不那么规则一些
  • 给粑粑添加随机角度
  • 让中心粑粑更趋向于爆炸口
  • 添加链式动画出现和下落

4.所有人震动

这个功能只需要用简单 css 动画就能完成这里就不再详细讲啦,有兴趣的小伙伴可以实现一下放到评论里~

本次纯属于一个对这个效果好奇的探索,不是100%还原动画。本人也不是专门写动画的,上述库也是第一次使用,写的可能不那么专业(有任何问题欢迎评论区指出错误)。但是希望能给大家提供一个好玩的思路,在做动画的时候可以利用 lottietween 两个库,以及将复杂问题简单化,把不规律的东西变成规律的东西,把复杂的东西变成简单的,最后再一步步地去深化。同时感谢楠溪对本文的校对。

回看笔者往期高赞文章,也许能收获更多喔!

❤️关注+点赞+收藏+评论+转发❤️ ,原创不易,鼓励笔者创作更好的文章

关注公众号秋风的笔记,一个专注于前端面试、工程化、开源的前端公众号


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK