实现效果
知识点
背景礼花飘落效果
实现方式其实有2种
- 通过创建标签,给随机动画(原生js可用)loop重复执行;
- 优先固定标签数量,给随机动画(适用于vue:v-for,小程序:wx:for)loop重复执行,小程序上如何实现会在下面介绍;
createElement创建标签
const [$body] = utils.$('.body')
const bgFlowerNum = 200 // 背景礼花数量
const bgFlowerSize = 10 // 背景礼花大小
...
// 创建公用小花瓣
const createFireworksDot = ({ top, left, scale }) => {
const $el = document.createElement('div')
scale = scale || utils.random(0.2, 1.2, 1)
// 给标签设置样式属性
utils.set($el, {
width: `${bgFlowerSize * scale}px`, // 随机定义大小尺寸
height: `${bgFlowerSize * scale}px`,
position: 'absolute', // 定义初始化位置信息
top,
left,
transformStyle: 'preserve-3d', // 定义3D旋转效果
// 随机获取一种彩带颜色
background: utils.randomPick([
'#dce90d',
'#0de9be',
'#0dbce9',
'#e9530d',
'#e90d59',
'#ffffff'
]),
// 随机定义彩带是正方形还是圆形
borderRadius: utils.randomPick(['50%', '0%']),
userSelect: 'none'
})
// $el.classList.add('bg-flower') // 可忽略,因为已经提前设置好样式属性,其次通过优先定义class的方式不会生效
return $el
}
...
// 根据设置的最大礼花数量,初始化标签
const createBgFlower = () => {
const bounds = $body.getBoundingClientRect()
const { width, height, left, top } = bounds
for (let i = 0; i < bgFlowerNum; i++) {
const scale = utils.random(0.2, 1.2, 1)
// 初始化位置坐标,左右偏移为 0~body最大宽度,上下偏移为 -50~-20
const $el = createFireworksDot({
left: `${utils.random(0, width)}px`,
top: `${utils.random(-50, -20)}px`,
scale
})
$body.appendChild($el)
...
}
}
添加anime动画让彩带动起来
// 创建背景礼花下落动画
const createBgFlower = () => {
...
for (let i = 0; i < bgFlowerNum; i++) {
...
$body.appendChild($el)
const animation = animate($el, {
translateX: [
{
from: utils.random(-200, 200),
to: utils.random(-10, 10),
ease: 'out'
},
{ to: utils.random(-100, 100), ease: 'inOut(2)' }
],
translateY: [{ to: utils.random(height + 50, height + 100), ease: 'inOut(2)' }],
duration: (6000 - 2000 * scale) / bgFlowerSpeed, // 根据彩带大小控制运动速度,彩带越大下落越快
rotate: `${utils.random(1, 3)}turn`, // 旋转角度 1turn = 360°
rotateX: `${utils.random(1, 3)}turn`,
rotateY: `1turn`,
// scale: [1, 1.2, 1, 0.1],
ease: 'inOut(2)',
delay: utils.snap(utils.random(-5000, 5000), 500) / bgFlowerSpeed,
loop: true // 重复执行
})
bgFlowerArr.push({ $el, animation }) //将标签和动画添加到bgFlowerArr数组中,方便后期重置动画
}
}
窗口变化重新渲染动画
let bgFlowerArr = []; // 彩带标签动画集合数组
...
const draw = () => {
// 每次重绘前清空背景元素和动画
bgFlowerArr.forEach(({ $el, animation }) => {
animation.revert()
$body.removeChild($el)
})
bgFlowerArr = []
createBgFlower()
}
// 监听窗口尺寸改变重绘
window.addEventListener('resize', () => {
draw()
})
点击按钮喷洒礼花🎉
前期工作准备充分后,点击按钮喷洒的效果也就方便实现了
初始化礼花位置
<div class="body">
<div class="anime-box">
<div class="button">🎉 click me</div>
</div>
</div>
const [$animeBox] = utils.$('.anime-box')
...
// 点击按钮时初始化礼花位置
const createButtonAnimation = () => {
const bounds = $animeBox.getBoundingClientRect()
const { width, height, left, top } = bounds
// 这里的位置取按钮的中心位置
const hw = width / 2
const hh = height / 2
// 这里的喷洒数量可自行控制
for (let i = 0; i < 20; i++) {
const $el = createFireworksDot({
left: `${hw}px`,
top: `${hh}px`
})
$animeBox.appendChild($el)
}
}
添加anime动画执行喷洒效果
// 点击按钮时执行礼花爆开动画
const createButtonAnimation = () => {
...
for (let i = 0; i < 20; i++) {
const $el = createFireworksDot({
left: `${hw}px`,
top: `${hh}px`
})
$animeBox.appendChild($el)
const scale = utils.random(0.2, 1.2, 1)
animate($el, {
translateX: [
{
to: utils.random(-50, 50),
ease: 'out'
},
{ to: utils.random(-100, 100), ease: 'inOut(2)' }
],
translateY: [
{
to: utils.random(-200, 0),
ease: 'out'
},
{ to: utils.random(height, height + 50), ease: 'inOut(2)' }
],
duration: 1000 * (1 - scale) + 1000,
rotate: `${utils.random(1, 3)}turn`,
rotateX: `${utils.random(1, 3)}turn`,
rotateY: `1turn`,
scale: [1, 1.2, 1, 0.3],
opacity: [1, 1, 1, 0],
ease: 'inOut(2)'
}).then(() => {
// 动画执行完成后移除标签
$animeBox.removeChild($el)
})
}
}
微信小程序如何实现彩带散落效果
当然因为小程序不支持dom操作,所以不能直接用以上方式进行实现,如果你能接受以下问题的话(😂作者劝退,能不在小程序上花拳绣腿就不要作死了):
-
animejs不支持在微信小程序上使用,尝试npm,js引入均会报错!!! -
微信小程序上ios不兼容
transform-style: 'preserve-3d'效果,如下图所示(左-开发工具,安卓彩带飘落正常)(右-ios飘落无3D效果):
如果以上都还没劝退你,想必你很有执念,祝兄台一路平安🙇~
下面开始骚操作,没用animejs也能大搞特搞!!
实现原理
- 正常通过
wx:for创建多个标签 - 优先定义好彩带
.dot的css样式属性,先把公用的一些属性给定义好,再实现定义好动画样式,后期通过随机定义的dotMove动画,来匹配不同的dotMove1,dotMove2... 动画效果
.dot {
animation-name: dotMove;
animation-timing-function: ease-out;
animation-iteration-count: infinite;
transform-style: preserve-3d;
-webkit-transform-style: preserve-3d;
pointer-events: none;
opacity: 0.9;
}
@keyframes dotMove {
to {
transform: translate3d(-50px, 300px, 300px) rotate3d(1, 1, 1, 360deg);
}
}
@keyframes dotMove1 {
to {
transform: translate3d(50px, 300px, 300px) rotate3d(1, 1, 1, 720deg);
}
}
@keyframes dotMove2 {
to {
transform: translate3d(100px, 300px, 300px) rotate3d(1, 1, 1, 360deg);
}
}
@keyframes dotMove3 {
to {
transform: translate3d(-100px, 300px, 300px) rotate3d(1, 1, 1, 720deg);
}
}
- 创建时通过定义动画属性
animation-delay,animation-name等,方便在view标签上给style属性赋值;
- 标签通过
style结合不同的left,top,animation-name,animation-delay...来达到随机的动画效果
实现代码
<view class="container">
<view class="dot" wx:for="{{bgFlowerArr}}" style="{{item.styleStr}}"></view>
</view>
data: {
bgFlowerArr: [] //礼花集合数组
},
...
// 创建礼花动画
playAnimation() {
const _this = this
// 通过小程序提供的api,获取.container容器的宽高
const query = wx.createSelectorQuery();
query.select('.container').boundingClientRect();
query.exec((res) => {
const bounds = res[0]
const bgFlowerSpeed = 0.7 // 背景礼花飘落速度
const bgFlowerNum = 88 // 背景礼花数量
const bgFlowerSize = 8 // 背景礼花大小
const {
width,
height
} = bounds
let arr = []
for (let i = 0; i < bgFlowerNum; i++) {
const scale = utils.random(0.2, 1.4, 1)
// 定义随机样式
const style = {
width: `${bgFlowerSize * scale}px`,
height: `${bgFlowerSize * scale}px`,
position: 'absolute',
left: `${utils.random(0, width)}px`,
top: `${utils.random(-100, -10)}px`,
background: utils.randomPick([
'#dce90d',
'#0de9be',
'#0dbce9',
'#e9530d',
'#e90d59',
'#ffffff'
]),
transform: `translate3d(0px, 0px, 0px) rotate3d(1, 1, 1, 0deg)`,
// 因为这里不能用animejs的属性,所以直接用原生属性
'animation-delay': utils.randomPick([-4000, -3000, -2000, -1000, 1000, 2000]) / bgFlowerSpeed,
'animation-name': utils.randomPick(['dotMove', 'dotMove1', 'dotMove2', 'dotMove3']),
'animation-duration': `${(6000 - 2000 * scale) / bgFlowerSpeed}ms`
}
// 将style对象属性转化为字符串
const styleStr = this.styleToStr(style)
arr.push({
styleStr
})
}
_this.setData({
bgFlowerArr: arr
})
})
}
styleToStr(style) {
return Object.keys(style).reduce((acc, cur) => {
let value = `${cur}:${style[cur]};`
if (cur === 'animation-delay') {
value = `${cur}:${style[cur]}ms;`
}
acc += value
return acc
}, '')
},
.dot {
animation-name: dotMove;
animation-timing-function: ease-out;
animation-iteration-count: infinite;
transform-style: preserve-3d;
-webkit-transform-style: preserve-3d;
pointer-events: none;
opacity: 0.9;
}
@keyframes dotMove {
to {
transform: translate3d(-50px, 300px, 300px) rotate3d(1, 1, 1, 360deg);
}
}
@keyframes dotMove1 {
to {
transform: translate3d(50px, 300px, 300px) rotate3d(1, 1, 1, 720deg);
}
}
@keyframes dotMove2 {
to {
transform: translate3d(100px, 300px, 300px) rotate3d(1, 1, 1, 360deg);
}
}
@keyframes dotMove3 {
to {
transform: translate3d(-100px, 300px, 300px) rotate3d(1, 1, 1, 720deg);
}
}
基于animejs分离出来的utils工具方法
/**
* 从数组中随机选择一个元素
* @param {Array} array - 源数组
* @returns {*} 数组中的随机元素
*/
export const randomPick = (array) => {
if (!Array.isArray(array) || array.length === 0) {
return undefined;
}
const randomIndex = Math.floor(Math.random() * array.length);
return array[randomIndex];
}
/**
* 生成指定范围内的随机数
* @param {number} min - 最小值
* @param {number} max - 最大值
* @param {number} [round] - 可选,保留的小数位数
* @returns {number} 范围内的随机数
*/
export const random = (min, max, round) => {
if (min >= max) {
throw new Error('Min value must be less than max value');
}
const value = Math.random() * (max - min) + min;
if (round !== undefined) {
const factor = Math.pow(10, round);
return Math.round(value * factor) / factor;
}
return value;
}