多图预警 多图预警 多图预警
在网页中经常会见到效果酷炫的动态效果,如之前文章分享比较简单的动态可交互“粒子-线”效果,如下图:
网上也有比较多的教程,但是学习的最终成果,往往是各个独立的html文件,很难复用在其他项目中。
而如今,前端开发讲究模块化以及组件化,所以便想通过一定方式将其封装为模块或者组件,方便在其他项目中调用。考虑到如今比较流行React与Vue组件开发, 所以本文主要分享如何将之前的动效以模块和React及Vue组件的形式实现。
效果放前面,具体请戳源码:
使用很是简单,具体如下:
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1920, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>React BGAnimation</title>
<link rel='stylesheet' href='./index.css'></link>
</head>
<body>
<div id="container" class="txt-center">
<div class="left">
<header>
<h3>React组件</h3>
</header>
<main id="root" class="content"> </main>
</div>
<div class="right">
<header>
<h3>Vue组件</h3>
</header>
<main id="app" class="content">
<app></app>
</main>
</div>
</div>
<script src='./dist/react.build.js'></script>
<script src='./dist/vue.build.js'></script>
</body>
</html>
- Vue使用方法
<template>
<MouseMovingBubble :width='width' :height='height' :number='number' :style="style"></MouseMovingBubble>
</template>
<script>
export default {
data() {
return {
width: 600,
height: 500,
number: 10,
style: {
border: "1px solid lightgray"
}
};
}
};
</script>
- React使用方法
import React from "react";
import ReactDOM from "react-dom";
import * as Coms from "./components/index";
const mountDom = document.getElementById("root");
ReactDOM.render(
<Coms.MouseMovingBubble width="600" height="500" number={10} />,
mountDom
);
动画
我们都知道,动画只不过是一个短时间内连续绘制不同图形(帧)产生的效果.通过requestAnimationFrame
可以实现流畅的动画,因为浏览器内部对动画执行做了优化(与浏览器渲染同步);不建议使用setTimeInterval
,因为这是个不靠谱的家伙!
那么下边介绍一下我自己理解的一个动画的基本框架:
- 初始化(Init):完成一些数据的预处理和画布画笔的创建等
- 步进(Step):完成每一帧动画图形变化需要做的操作(如清空画布/计算新的位置等)
- 循环(Loop):完成动画的循环,即循环执行步进(Step)方法
是不是很简单呢?哈哈 下边动手实现以下:
所有动画父类:IAniamtion
通过分析完成一个动画效果的过程,我们可以抽象出一个基类(IAnimation),定义所有动画必须实现的方法以及对一下公共操作进行封装.
export default class IAnimation {
constructor(props) {
// 接收配置参数
this.canvas = props.canvas;
this.width = parseInt(props.width);
this.height = parseInt(props.height);
// 为方法绑定this
this.init = this.init.bind(this);
this.step = this.step.bind(this);
this.loop = this.loop.bind(this);
}
init() {
// 初始化画布和画笔
this.ctx = this.canvas.getContext("2d");
if (!this.ctx) throw "浏览器不支持Canvas,请使用其他浏览器试试!";
this.canvas.width = this.width;
this.canvas.height = this.height;
}
loop() {
// 每次loop前清空之前画布的内容
this.ctx.clearRect(0, 0, this.width, this.height);
}
step() {
// 步进
}
}
"鼠标移动--气泡"动画实现
以"鼠标气泡"为例,简单说明实现动画的过程.因为逻辑不复杂,所以实现也比较简单.
import IAnimation from "./IAnimation";
import { Extent, Particle } from "../graphic";
import { random } from "../utils";
export default class MouseMovingBubbleAnimation extends IAnimation {
constructor(props) {
super(props);
// 每次鼠标移动产生的粒子数
this.number = props.number || 0;
this.oArray = [];
}
init() {
// 调用父类(IAnimation)的init方法
super.init();
var self = this;
// 监视鼠标移动事件
this.canvas.onmousemove = function(e) {
const mpos = { x: e.offsetX, y: e.offsetY };
for (let i = 0; i < self.number; i++) {
// 创建粒子
let oPr = new Particle();
oPr.range = new Extent([0, 0], [self.width, self.height]);
oPr.x = mpos.x;
oPr.y = mpos.y;
oPr.radius = random(8, 15);
oPr.speed = 20 / oPr.radius;
oPr.direction = random(0, 2 * Math.PI);
self.oArray.push(oPr);
}
};
// 执行动画
self.loop();
}
loop() {
// 判断粒子数
// 当例子说大于0的时候执行步进操作
if (this.oArray.length > 0) {
super.loop();
this.step();
}
requestAnimationFrame(this.loop);
}
step() {
// 调用父类步进(step)方法
super.step();
let deletAry = [];
this.oArray.forEach(p => {
// 粒子步进
p.step();
// 减小粒子半径
p.radius -= p.oldRadius / 50;
// 直到小于0 消失
if (p.radius <= 0) deletAry.push(p);
else p.draw(this.ctx);
});
// 移除消失的粒子
deletAry.forEach(p => {
let index = this.oArray.indexOf(p);
this.oArray.splice(index, 1);
});
}
}
React与Vue组件封装
其实在动画逻辑实现好了的情况下,封装组件是一件异常简单的事情(暂时不考虑复杂的逻辑或者交互),只需要在组件特定的生命周期执行动画实例的相应发方法即可.这里以为需要传入canvas DOM
作为动画参数,所以在组件挂载之后创建了动画实例,并执行了初始化方法.
具体以"鼠标移动--气泡"动画为例说明React和Vue封装:
MouseMovingBubble.jsx
import React from "react";
import { MouseMovingBubbleAnimation } from "../../../common/index";
export default class Particle extends React.Component {
constructor(props) {
super(props);
}
// 挂载后创建动画实例
componentDidMount() {
this.animation = new MouseMovingBubbleAnimation ({
canvas: this.oCanvas,
width: this.props.width,
height: this.props.height,
number: this.props.number
});
// 执行初始化方法
this.animation.init();
}
render() {
return (
<canvas style={this.props.style} ref={canvas => (this.oCanvas = canvas)}>
您的浏览器不支持Canvas,请使用其他浏览器试试看!
</canvas>
);
}
}
// 默认配置参数
Particle.defaultProps = {
width: 1080,
height: 512,
number: 10,
style: {
border: "1px solid lightgray"
}
};
MouseMovingBubble.vue
<template>
<canvas :width="width" :height="height">您的浏览器不支持Canvas,请使用其他浏览器试试看!</canvas>
</template>
<script>
import { MouseMovingBubbleAnimation } from "../../../common/animation/index";
export default {
// 默认参数配置
props: {
width: {
type: Number,
default: 300
},
height: {
type: Number,
default: 300
},
number: {
type: Number,
default: 30
}
},
mounted() {
// 组件挂载后创建动画实例
const animation = new MouseMovingBubbleAnimation ({
width: this.width,
height: this.height,
number: this.number,
canvas: this.$el
});
// 初始化动画
animation.init();
}
};
</script>
<style>
</style>
其他的效果实现,暂时不在这里抛代码了,感兴趣的可以去看源码,后面也会针对个别效果做详细介绍...
如果您感觉有所帮助,或者有问题需要交流,欢迎留言评论,非常感谢!
前端菜鸟,还请多多关照!