昨天下午,学弟突然发过来一个视频,问我怎么实现这样的特效(emmm,不知道咋上传视频,就录个gif吧)
效果也比较简单,于是就自己着手用canvas实现一下。
首先,我们需要设计一下小球对象的数据结构,小球的半径设置为r,颜色设置为color,由于其位置是不断移动的,必须要有x和y坐标。其次,小球可以左移,也可以右移,说明小球要有速度vx和vy。最后小球点击可以停止或者继续移动,需要添加一个属性isMoving来记录当前状态。如下:
x: x轴坐标
y: y轴坐标
r: 半径
vx: x的速度
vy: y的速度
color: 颜色
isMoving: 记录
canvas实现这样的特效其实还是比较简单的,绘制图形或者添加文字都有专门的api,很是方便。canvas学习–文本操作 - 掘金 (juejin.cn)和canvas学习–曲线图形 - 掘金 (juejin.cn)这两篇文章是我之前写的学习文章,感兴趣的掘友可以查看。
let width = 600;
let height = 600;
window.onload = function () {
//写逻辑
let canvas = document.getElementById('canvas');
let cxt = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
let ballCount = 1; // 小球数量
let balls = []; // 存储小球的数组
let colorArr = ['red', 'orange', 'yellow', 'green', 'teal', 'blue', 'purple']; //小球颜色
let radius = Math.floor(Math.random() * 10) + 10;
for (let i = 0; i < ballCount; i++) {
balls.push({
x: Math.floor(Math.random() * (width - radius - width / 3)) + width / 3, // 小球的x坐标
y: height - radius, // 小球的y坐标
r: radius, // 小球半径
vx: Math.floor(Math.random() * 2) - 2, // 水平偏移量
vy: Math.floor(Math.random() * 2) + 2, // 垂直偏移量
color: colorArr[Math.floor(Math.random() * 7)],
isMoving: false, // 小球的状态,false代表移动,true代表停止
})
}
function drawBall() { // 绘制小球
cxt.clearRect(0, 0, width, height); // 清除画布
for (let i = 0; i < ballCount; i++) {
cxt.beginPath();
let text = "Rust";
let len = cxt.measureText(text).width;
let ball = balls[i];
cxt.moveTo(ball.x, ball.y);
cxt.fillStyle = ball['color'];
cxt.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2, true);
cxt.fill();
cxt.font = "bold 15px 楷体";
cxt.fillStyle = '#000';
cxt.fillText(text, ball.x - len / 2, ball.y);
cxt.closePath();
}
}
drawBall();
}
小球上浮的话,其实就是x和y坐标的不断变化,我们只需要遍历存储小球的数组,调整x和y就行,这里需要开启一个定时线程。
function moveBall() { // 移动小球
for (let i = 0; i < ballCount; i++) {
let ball = balls[i];
if (!ball.isMoving) {
ball.x = ball.x + ball.vx;
ball.y = ball.y - ball.vy;
if (ball.r <= 30) { // 小球上升逐渐变大
ball.r = ball.r + 0.1;
}
if (ball.x <= ball.r) { // 到达x轴边界消失
balls[i] = {
x: ball.x,
y: -2 * ball.y,
r: ball.r,
vx: 0,
vy: 0,
color: ball.color,
isMoving: true,
}
}
if (ball.y <= ball.r) { // 到达y轴边界消失
balls[i] = {
x: ball.x,
y: -2 * ball.y,
r: ball.r,
vx: 0,
vy: 0,
color: ball.color,
isMoving: true,
}
}
}
}
}
setInterval(drawBall, 50);
这里只需要监听点击事件,判断是否点中小球即可
canvas.onclick = function (e) {
// 获取点击坐标
let clickX = e.offsetX;
let clickY = e.offsetY;
for (let i = 0; i < balls.length; i++) {
if (clickX < balls[i].x + balls[i].r && clickX > balls[i].x - balls[i].r) { // 判断是否在圆内
if (clickY < balls[i].y + balls[i].r && clickY > balls[i].y - balls[i].r) {
balls[i].isMoving = !balls[i].isMoving;
}
}
}
}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<canvas id="canvas" width="300" height="300" style="border: 1px dashed gray;">canvas>
body>
<script>
let width = 600;
let height = 600;
window.onload = function () {
//写逻辑
let canvas = document.getElementById('canvas');
let cxt = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
let ballCount = 5; // 小球数量
let balls = []; // 存储小球的数组
let colorArr = ['red', 'orange', 'yellow', 'green', 'teal', 'blue', 'purple']; //小球颜色
let radius = Math.floor(Math.random() * 10) + 10;
for (let i = 0; i < ballCount; i++) {
balls.push({
x: Math.floor(Math.random() * (width - radius - width / 3)) + width / 3, // 小球的x坐标
y: height - radius, // 小球的y坐标
r: radius, // 小球半径
vx: Math.floor(Math.random() * 2) - 2, // 水平偏移量
vy: Math.floor(Math.random() * 2) + 2, // 垂直偏移量
color: colorArr[Math.floor(Math.random() * 7)],
isMoving: false, // 小球的状态,false代表移动,true代表停止
})
}
//console.log(balls);
canvas.onclick = function (e) {
// 获取点击坐标
let clickX = e.offsetX;
let clickY = e.offsetY;
for (let i = 0; i < balls.length; i++) {
if (clickX < balls[i].x + balls[i].r && clickX > balls[i].x - balls[i].r) { // 判断是否在圆内
if (clickY < balls[i].y + balls[i].r && clickY > balls[i].y - balls[i].r) {
balls[i].isMoving = !balls[i].isMoving;
}
}
}
}
function drawBall() { // 绘制小球
cxt.clearRect(0, 0, width, height); // 清楚画布
for (let i = 0; i < ballCount; i++) {
cxt.beginPath();
let text = "Rust";
let len = cxt.measureText(text).width;
let ball = balls[i];
cxt.moveTo(ball.x, ball.y);
cxt.fillStyle = ball['color'];
cxt.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2, true);
cxt.fill();
cxt.font = "bold 15px 楷体";
cxt.fillStyle = '#000';
cxt.fillText(text, ball.x - len / 2, ball.y);
cxt.closePath();
}
moveBall();
}
function moveBall() { // 移动小球
for (let i = 0; i < ballCount; i++) {
let ball = balls[i];
if (!ball.isMoving) {
ball.x = ball.x + ball.vx;
ball.y = ball.y - ball.vy;
if (ball.r <= 30) { // 小球上升逐渐变大
ball.r = ball.r + 0.1;
}
if (ball.x <= ball.r) { // 到达x轴边界消失
balls[i] = {
x: ball.x,
y: -2 * ball.y,
r: ball.r,
vx: 0,
vy: 0,
color: ball.color,
isMoving: true,
}
}
if (ball.y <= ball.r) { // 到达y轴边界消失
balls[i] = {
x: ball.x,
y: -2 * ball.y,
r: ball.r,
vx: 0,
vy: 0,
color: ball.color,
isMoving: true,
}
}
}
}
}
setInterval(drawBall, 50);
}
script>
html>
效果图如下:
export function snow(canvas) {
var ctx = canvas.getContext("2d");
var W = window.innerWidth;
var H = window.innerHeight;
canvas.width = W;
canvas.height = H * 0.12;
var flakesCount = 100;
var flakes = []; // flake instances
for (var i = 0; i < flakesCount; i++) {
flakes.push({
x: Math.random() * W,
y: Math.random() * H,
r: Math.random() * 5 + 2, // 2px - 7px
d: Math.random() + 1
});
}
function drawFlakes() {
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = "#fff";
ctx.beginPath();
for (var i = 0; i < flakesCount; i++) {
var flake = flakes[i];
ctx.moveTo(flake.x, flake.y);
ctx.arc(flake.x, flake.y, flake.r, 0, Math.PI * 2, true);
}
ctx.fill();
moveFlakes();
}
var angle = 0;
function moveFlakes() {
angle += 0.01;
for (var i = 0; i < flakesCount; i++) {
var flake = flakes[i];
flake.y += Math.pow(flake.d, 2) + 1;
flake.x += Math.sin(angle) * 2;
if (flake.y > H) {
flakes[i] = { x: Math.random() * W, y: 0, r: flake.r, d: flake.d };
}
}
}
setInterval(drawFlakes, 50);
}
export function rain(canvas) {
var ctx = canvas.getContext("2d");
var w = window.innerWidth;
var h = window.innerHeight;
canvas.width = w;
canvas.height = h * 0.12;
ctx.strokeStyle = "rgba(174,194,224,0.5)";
ctx.lineWidth = 1;
ctx.lineCap = "round";
var init = [];
var maxParts = 200;
for (var a = 0; a < maxParts; a++) {
init.push({
x: Math.random() * w,
y: Math.random() * h,
l: Math.random() * 1,
xs: -4 + Math.random() * 4 + 2,
ys: Math.random() * 10 + 10
});
}
var particles = [];
for (var b = 0; b < maxParts; b++) {
particles[b] = init[b];
}
function draw() {
ctx.clearRect(0, 0, w, h);
for (var c = 0; c < particles.length; c++) {
var p = particles[c];
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(p.x + p.l * p.xs, p.y + p.l * p.ys);
ctx.stroke();
}
move();
}
function move() {
for (var b = 0; b < particles.length; b++) {
var p = particles[b];
p.x += p.xs;
p.y += p.ys;
if (p.x > w || p.y > h) {
p.x = Math.random() * w;
p.y = -20;
}
}
}
setInterval(draw, 50);
}
下雨和小球上浮原理基本上都是一样的,需要的掘友可以阅读一下上面的代码或者导入到自己的工程中。