一、 图片获取点,实现点击散开后聚集的动画特效
(1)代码
<template>
<div class="logo-background">
<canvas id="canvas" v-if="loading" />
<div class="sideBox" ref="sideBox" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
<point :data="item" v-for="(item, index) in point" :key="index" />
</div>
</div>
</template>
<script>
import point from "./point";
export default {
data() {
return {
//用于显示隐藏canvas
loading: true,
point: [],
//聚集状态的动画数据
originAnimation: {
x: 0,
y: 0,
opacity: 1,
scale: 1
},
//图片src
src: require("../assets/logoFill.svg")
};
},
components: {
point
},
created() {},
mounted() {
this.createPointData();
},
methods: {
//散开点
handleMouseEnter() {
const rect = document.documentElement.getBoundingClientRect();
const sideRect = this.$refs.sideBox.getBoundingClientRect();
const sideTop = sideRect.top - rect.top;
const sideLeft = sideRect.left - rect.left;
this.point = this.point.map(item => ({
...item,
animation: {
x: Math.random() * rect.width - sideLeft - item.x,
y: Math.random() * rect.height - sideTop - item.y,
opacity: Math.random() * 0.4 + 0.1,
scale: Math.random() * 2.4 + 0.1
}
}));
},
//聚集点
handleMouseLeave() {
this.point = this.point.map(item => ({
...item,
animation: this.originAnimation
}));
},
//获取图像数据点
createPointData() {
const w = 200;
const h = 150;
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, w, h);
canvas.width = w;
canvas.height = h;
const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, w, h);
const data = ctx.getImageData(0, 0, w, h).data;
this.setDataToDom(data, w, h);
};
img.crossOrigin = "anonymous";
img.src = this.src;
},
//生成具体点数据
setDataToDom(data, w, h) {
this.pointArray = [];
const number = 12;
for (let i = 0; i < w; i += number) {
for (let j = 0; j < h; j += number) {
if (data[(i + j * w) * 4 + 3] > 150) {
this.pointArray.push({ x: i, y: j });
}
}
}
this.pointArray.forEach((item, i) => {
const r = Math.random() * 10 + 6;
const b = Math.random() * 0.4 + 0.1;
this.point.push({
x: item.x,
y: item.y,
r:r,
b: b,
animation: {
x: 0,
y: 0,
opacity: 1,
scale: 1
}
});
});
this.loading = false;
}
}
};
</script>
<style>
.logo-background {
width: 100%;
height: 100%;
/* overflow: hidden; */
}
.sideBox {
position: relative;
width: 300px;
height: 300px;
}
</style>
(2)point.vue
<template>
<div
:class="$style['point-wrapper']"
:style="`top:${data.y}px;left:${data.x}px;opacity:${data.animation.opacity};transform:translate(${data.animation.x}px,${data.animation.y}px) scale(${data.animation.scale});`"
>
<div
:class="[
$style.point,
{
[$style.pointAnimation]: this.status === 'gather',
},
]"
:style="`width:${data.r}px;height:${data.r}px;transform:translate(${floatX}px,${floatY}px)`"
></div>
</div>
</template>
<script>
import { Component, Vue, Prop, Watch } from 'vue-property-decorator';
@Component()
export default class Point extends Vue {
@Prop({ default: () => ({}), type: Object }) data;
@Prop({ default: 'gather', type: String }) status;
floatX = 0;
floatY = 0;
timer = '';
//聚集时显示悬浮动画,散开显示散开动画
@Watch('status')
handleStatusChange(status) {
if (status === 'gather') {
this.floatX = 0;
this.floatY = 0;
clearInterval(this.timer);
} else {
this.startDisperseAnimation();
}
}
startDisperseAnimation() {
this.timer = setInterval(() => {
this.floatX = Math.random() * 5;
this.floatY = Math.random() * 5;
}, 1000);
}
}
</script>
<style lang="less" module>
.point-wrapper {
position: absolute;
transition: transform 1s linear;
}
.point {
position: relative;
// background-color: rgb(15, 15, 15);
background-color: var(--logo-color);
border-radius: 50%;
transition: transform 1s linear;
}
.pointAnimation {
-webkit-animation: ghostUpdown 2s infinite alternate;
}
@keyframes ghostUpdown {
from {
}
to {
transform: translate(0px, -10px);
}
}
</style>
二、原理
(1)使用canvas获取图片像素点(ctx.getImageData),然后图片取点像素为控制点的个数,以图片宽度除以像素点来决定点的个数, 默认为 20, 每行每列为15个取点。
(2)使用div绘制点,每个点外部包裹一层父级div。父级div用于实现聚散动画,点div用于实现悬浮动画。
(3)父级div聚散动画使用transform:translate(x,y)实现聚散动画,使用transition: transform 1s linear;实现动画平滑过渡。使用position定位实现点的排布,具体top和left由(1)获取计算得出。
<div
:class="$style['point-wrapper']"
:style="`top:${data.y}px;left:${data.x}px;opacity:${data.animation.opacity};transform:translate(${data.animation.x}px,${data.animation.y}px) scale(${data.animation.scale});`"
>
</div>
.point-wrapper {
position: absolute;
transition: transform 1s linear;
}
(4)点div使用annimation实现悬浮动画
.pointAnimation {
-webkit-animation: ghostUpdown 2s infinite alternate;
}
@keyframes ghostUpdown {
from {
}
to {
transform: translate(0px, -10px);
}
}
三、效果
(1)聚集时(一张logo)
(2)散开时(散开距离自行调整,这里为了截图方便改小了距离)