效果一瞥
使用 snap-fade-away 能够直接灭霸动效
Bye bye
imcuttle 🐟
import snapFadeAway from 'snap-fade-away'
export default class extends Component {
state = {
animating: false
}
nodeRef;
fadeAway = async () => {
this.setState({animating: true})
await snapFadeAway(this.nodeRef)
this.setState({animating: false})
}
reset = () => {
this.nodeRef.style.visibility = 'visible'
}
render() {
return <div>
<button disabled={this.state.animating} onClick={this.fadeAway}>FadeAway</button>
<button disabled={this.state.animating} onClick={this.reset}>Reset</button>
<div style={{textAlign:"center"}} ref={ref => this.nodeRef = ref}>
<h4>Bye bye</h4>
<p> imcuttle 🐟 </p>
<img src="data:image/jpeg;base64,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"/>
</div>
</div>
}
}
实现原理
下面说明一下 snap-fade-away 的实现要点
首先进行 “DOM粒子化分割”
把输入的 DOM 元素转换为 canvas 画布,得到图像像素点数据,进而进行粒子化分割,如下效果展示: 其中红色表示 原DOM元素 ;绿色表示 转换后的canvas ;蓝色表示 被粒子分割的n个canvas ,同时点击蓝色块可以聚合或分离,点击可以聚合在一起查看效果。
ABCDEFGHIJKLMNOPQRST 💯import toCanvas from 'html2canvas'
import React from 'react'
const splitFrames = (canvas, frameCount) => {
const ctx = canvas.getContext('2d')
const { width, height } = canvas
// 获取 canvas 的像素数据
const originalFrame = ctx.getImageData(0, 0, width, height)
const frames = new Array(frameCount).fill({}).map(() => ctx.createImageData(width, height))
// 将原始的像素数据,随机分散到多个canvas上面,粒子化
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
// 随机获取 像素对象
const frameIndex = Math.floor(Math.random() * frameCount)
// 当前的像素位置:通过 x、y 计算出当前遍历到哪个像素点,实际就是从左到右,一行一行的遍历
const pixelIndex = 4 * (y * width + x)
// 一个像素 rgba,所以需要设置 4 个值
for (let z = 0; z < 4; z++) {
frames[frameIndex].data[pixelIndex + z] = originalFrame.data[pixelIndex + z]
}
}
}
return frames
}
export default class extends React.Component {
frameCount = 10
canvasNodes = []
componentDidMount() {
this.initialize()
}
async initialize() {
const container = this.containerRef
const mirror = this.mirrorRef
const node = this.nodeRef
const canvas = await toCanvas(node)
const frames = splitFrames(canvas, this.frameCount)
mirror.appendChild(canvas)
canvas.style.border = '1px solid green'
canvas.style.marginBottom = '10px'
this.canvasNodes = frames.map((item, i) => {
const cloneNode = canvas.cloneNode(true)
cloneNode.getContext('2d').putImageData(item, 0, 0)
cloneNode.style.marginBottom = '4px'
cloneNode.style.border = '1px solid blue'
container.appendChild(cloneNode)
this.setUpEachCanvas(cloneNode, i)
return cloneNode
})
}
setUpEachCanvas(cloneNode) {
cloneNode.addEventListener('click', () => {
if ('absolute' === cloneNode.style.position) {
cloneNode.style.position = ''
cloneNode.style.left = ''
cloneNode.style.top = ''
} else {
cloneNode.style.position = 'absolute'
cloneNode.style.left = '0'
cloneNode.style.top = '0'
}
})
}
render() {
return <div>
<div style={{ border: '1px solid red', marginBottom: 10 }}>
<div ref={r => this.nodeRef = r}>ABCDEFGHIJKLMNOPQRST 💯</div>
</div>
<div ref={r => this.mirrorRef = r}/>
<div ref={r => this.containerRef = r} style={{ position: 'relative' }}>
</div>
</div>
}
}
进行动画
然后使用你喜欢的方式来随意定义你的动画吧! 下面例子是使用 transition 实现。
ABCDEFGHIJKLMNOPQRST 💯import SplitFrame from '@/about-snap-fade-away/split-frames'
const duration = '1.5s'
export default class extends SplitFrame {
setUpEachCanvas(node, i) {
if (i !== 0) {
Object.assign(node.style, {
position: 'absolute',
left: '0',
top: '0'
})
}
Object.assign(node.style, {
border: 'none',
transition: `transform ${duration} ease-out ${i /
this.frameCount}s, opacity ${duration} ease-out`,
userSelect: 'none',
pointerEvents: 'none',
opacity: 1,
transform: 'rotate(0deg) translate(0px)'
})
}
state = {
...super.state,
key: 0
}
animate = () => {
this.canvasNodes.forEach((item, i) => {
if (i !== 0) {
let base = i % 2 === 0 ? 1 : -1
let tx = base * Math.random() * i
let rotate = -base * i * Math.random()
item.style.transform = `rotate(${rotate}deg) translate(${tx}px)`
}
item.style.opacity = 0
})
}
reset = () => {
this.setState(
({ key }) => ({ key: key + 1 }),
() => {
this.initialize()
}
)
}
render() {
return (
<div>
<button onClick={this.animate}>Animate It!</button>
<button onClick={this.reset}>Reset!</button>
<div key={this.state.key}>{super.render()}</div>
</div>
)
}
}
如何进行动画触发
先看一个例子,如下代码执行会有什么效果呢?
dom.style.opacity = 0
dom.style.transition = 'opacity 1s ease'
dom.style.opacity = 1
在书写上述动效的时候遇到一个问题,如何 立即 进行动画的触发呢?
下例子分别对各种情况进行了实践
ABCDEFGHIJKLMNOPQRST 💯import SplitFrame from '@/about-snap-fade-away/split-frames'
const duration = '2.5s'
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
const raf = ms => new Promise(resolve => requestAnimationFrame(resolve))
export default class extends SplitFrame {
async initialize() {
await super.initialize()
await this.onAfterInitialized()
}
setUpEachCanvas(node, i) {
if (i !== 0) {
Object.assign(node.style, {
position: 'absolute',
left: '0',
top: '0'
})
}
Object.assign(node.style, {
border: 'none',
transition: `transform ${duration} ease-out ${i /
this.frameCount}s, opacity ${duration} ease-out`,
userSelect: 'none',
pointerEvents: 'none',
opacity: 1,
transform: 'rotate(0deg) translate(0px)'
})
}
async onAfterInitialized() {
// 根据不同 method 触发动画
switch (this.state.method) {
case 'setTimeout': {
await delay()
break
}
case 'RAF': {
await raf()
break
}
case 'RAF*2': {
await raf()
await raf()
break
}
case 'GCS-layout': {
getComputedStyle(this.nodeRef).margin
break
}
case 'GCS-painting': {
getComputedStyle(this.nodeRef).transform
break
}
case 'offsetLeft': {
this.nodeRef.offsetLeft
break
}
}
this.animate()
}
state = {
...super.state,
key: 0,
method: null
}
animate = () => {
this.canvasNodes.forEach((item, i) => {
if (i !== 0) {
let base = i % 2 === 0 ? 1 : -1
let tx = base * Math.random() * i
let rotate = -base * i * Math.random()
item.style.transform = `rotate(${rotate}deg) translate(${tx}px)`
}
item.style.opacity = 0
})
}
reset = (method) => () => {
this.setState(
({ key }) => ({ key: key + 1, method }),
() => {
this.initialize()
}
)
}
render() {
return (
<div>
<button onClick={this.reset(null)}>Use *nothing*</button>
<button onClick={this.reset('setTimeout')}>Use setTimeout</button>
<button onClick={this.reset('RAF')}>Use requestAnimationFrame</button>
<button onClick={this.reset('RAF*2')}>Use requestAnimationFrame * 2</button>
<button onClick={this.reset('GCS-layout')}>Use getComputedStyle layout</button>
<button onClick={this.reset('GCS-painting')}>Use getComputedStyle painting</button>
<button onClick={this.reset('offsetLeft')}>Use offsetLeft</button>
<div key={this.state.key}>{super.render()}</div>
</div>
)
}
}
在第五届 CSS 大会中,就有大佬分享过该 topic
实际应用
趁着复仇者联盟4的上映,Google 也即时加上了彩蛋, 在 Google 搜索 “Thanos” (灭霸) ,点击金手指出现动效