Canvas 08
我们要实现动画的效果,就需要把之前所有的东西进行重绘。
动画实现的基本步骤
- 清空canvas画布内容。(clearRect)
- 保存canvas状态。确保每一帧的渲染不受上一帧的影响。
- 绘制动画图形。
- 恢复canvas状态。
操控动画
因为我们的画面是在js执行完之后生成,所以我们需要的是在一个周期内执行相关绘制函数。 可以通过三种方法控制在设定的时间点上执行重绘函数。
- setInterval() 设置好时间间隔后,函数会定期执行。
- setTimeout() 在设置好时间之后执行函数。
- requestAnimationFrame() 在浏览器的重绘之前执行相关函数更新动画。
window.requestAnimationFrame() 实现动画效果,这个方法提供了更加平缓和高效的方式来执行动画,并且是在准备好了重绘条件时候,才会执行动画帧的绘制。一秒钟回调函数执行60次。
太阳星系运动的动画
ctx.globalCompositeOperation = “destination-over”;设置动画组合操作的类型 destination-over 它使得新的绘制内容被放置在现有内容的
用于绘制当前坐标系原点的方法
// 绘制坐标原点
ctx.beginPath()
ctx.moveTo(3, 0);
ctx.arc(0,0,3,0,2*Math.PI,true)
ctx.fill()
我们这里使用beginPath方法开启一个新的路径,避免绘制的形状和之前的路径相互影响。通过beginPath可以开启一个新的路径,会自动结束上一个路径。
const sun = new Image();// 都替换成填充圆形
const moon = new Image();
const earth = new Image();
const ctx = document.getElementById("canvas").getContext("2d");
function init() {
// sun.src = "canvas_sun.png";
// moon.src = "canvas_moon.png";
// earth.src = "canvas_earth.png";
window.requestAnimationFrame(draw);
}
function draw() {
ctx.globalCompositeOperation = "destination-over";
ctx.clearRect(0, 0, 300, 300); // clear canvas
ctx.fillStyle = "rgb(0 0 0 / 40%)";
ctx.strokeStyle = "rgb(0 153 255 / 40%)";
ctx.save();
ctx.translate(150, 150);
// 绘制坐标原点
ctx.beginPath()
ctx.moveTo(3, 0);
ctx.arc(0,0,3,0,2*Math.PI,true)
ctx.fill()
// Earth 获取当前秒 获取地球自转的角度 360/60 6
const time = new Date();
ctx.rotate(Math.PI/30*time.getSeconds()+Math.PI/30000*time.getMilliseconds())
ctx.translate(105, 0);
// 绘制坐标原点
ctx.beginPath()
ctx.moveTo(3, 0);
ctx.arc(0,0,3,0,2*Math.PI,true)
ctx.fill()
ctx.beginPath()
ctx.fillRect(0, -12, 40, 24); // Shadow
ctx.save();
ctx.fillStyle="red"
ctx.arc(0,0,12,0,2*Math.PI,true)
ctx.fill()
ctx.restore();
// Moon
ctx.save();
ctx.rotate(
((2 * Math.PI) / 6) * time.getSeconds() +
((2 * Math.PI) / 6000) * time.getMilliseconds(),
);
ctx.translate(0, 32);
// 绘制坐标原点
ctx.beginPath()
ctx.moveTo(3, 0);
ctx.arc(0,0,3,0,2*Math.PI,true)
ctx.fill()
ctx.save();
ctx.fillStyle="blue"
ctx.arc(0,0,6,0,2*Math.PI,true)
ctx.fill()
ctx.restore();
ctx.restore();
ctx.restore();
ctx.beginPath();
ctx.arc(150, 150, 105, 0, Math.PI * 2, false); // Earth orbit
ctx.stroke();
ctx.beginPath();
ctx.fillStyle="orange"
ctx.arc(150,150,40,0,2*Math.PI,true)
ctx.fill()
ctx.fillStyle = "rgb(0 0 0)";
ctx.fillRect(0, 0, 300, 300); // clear canvas
window.requestAnimationFrame(draw);
}
init();
关于钟表的动画
function clockTita(){
const ctx = document.getElementById("canvas").getContext("2d");
ctx.globalCompositeOperation = "destination-over";
ctx.save()
ctx.clearRect(0, 0, 150, 150);
ctx.translate(75,75)
ctx.rotate(-Math.PI / 2);
ctx.moveTo(60,0)
ctx.strokeStyle="rgb(50, 95, 162)"
ctx.lineWidth=6
ctx.arc(0,0,60,0,2*Math.PI)
ctx.stroke()
// 绘制秒针刻度
for(let i=0;i<60;i++){
ctx.save()
ctx.beginPath()
ctx.rotate((Math.PI*i*6)/180)
ctx.lineCap="round"
ctx.strokeStyle="rgb(0,0,0)"
ctx.lineWidth=1
ctx.moveTo(48,0)
ctx.lineTo(52,0)
ctx.stroke()
ctx.closePath()
ctx.restore()
}
// 绘制小时刻度
for(let i=0;i<12;i++){
ctx.save()
ctx.beginPath()
ctx.rotate((Math.PI*i*30)/180)
ctx.lineCap="round"
ctx.strokeStyle="rgb(0,0,0)"
ctx.lineWidth=2
ctx.moveTo(44,0)
ctx.lineTo(52,0)
ctx.stroke()
ctx.closePath()
ctx.restore()
}
// 绘制时针
const date=new Date()
let hours = date.getHours(); // 获取24小时制的小时
let minutes = date.getMinutes()
let seconds = date.getSeconds()
hours = hours % 12; // 24小时制转12小时制
hours = hours ? hours : 12; // 0小时变成12
ctx.save()
ctx.rotate((Math.PI*30*hours)/180+(Math.PI*30*minutes)/(180*60)+(Math.PI*30*seconds)/(180*3600))
ctx.beginPath()
ctx.moveTo(-6,0)
ctx.lineTo(38,0)
ctx.lineWidth=6
ctx.lineCap="round"
ctx.strokeStyle="rgb(0,0,0)"
ctx.stroke()
ctx.restore()
// 绘制分针
ctx.save()
ctx.beginPath()
ctx.rotate((Math.PI*6*minutes)/180+(Math.PI*6*seconds)/(180*60))
ctx.moveTo(-10,0)
ctx.lineTo(42,0)
ctx.lineWidth=4
ctx.lineCap="round"
ctx.strokeStyle="rgb(0,0,0)"
ctx.stroke()
ctx.restore()
// 绘制秒针
ctx.save()
ctx.beginPath()
ctx.rotate((Math.PI*6*seconds)/(180))
ctx.moveTo(-12,0)
ctx.lineTo(46,0)
ctx.lineWidth=2
ctx.lineCap="round"
ctx.strokeStyle="red"
ctx.stroke()
ctx.beginPath()
ctx.strokeStyle="red"
ctx.arc(46,0,4,0,2*Math.PI)
ctx.stroke()
ctx.restore()
ctx.restore()
window.requestAnimationFrame(clockTita)
}
window.requestAnimationFrame(clockTita)
全景图片
const img = new Image();
// User Variables - customize these to change the image being scrolled, its
// direction, and the speed.
img.src = "capitan_meadows_yosemite_national_park.jpg";
const canvasXSize = 800;
const canvasYSize = 200;
const speed = 30; // lower is faster
const scale = 1.05;
const y = -4.5; // vertical offset
// Main program
const dx = 0.75;
let imgW;
let imgH;
let x = 0;
let clearX;
let clearY;
let ctx;
img.onload = () => {
imgW = img.width * scale;
imgH = img.height * scale;
if (imgW > canvasXSize) {
// Image larger than canvas
x = canvasXSize - imgW;
}
// Check if image dimension is larger than canvas
clearX = Math.max(imgW, canvasXSize);
clearY = Math.max(imgH, canvasYSize);
// Get canvas context
ctx = document.getElementById("canvas").getContext("2d");
// Set refresh rate
return setInterval(draw, speed);
};
function draw() {
ctx.clearRect(0, 0, clearX, clearY); // clear the canvas
// If image is <= canvas size
if (imgW <= canvasXSize) {
// Reset, start from beginning
if (x > canvasXSize) {
x = -imgW + x;
}
// Draw additional image1
if (x > 0) {
ctx.drawImage(img, -imgW + x, y, imgW, imgH);
}
// Draw additional image2
if (x - imgW > 0) {
ctx.drawImage(img, -imgW * 2 + x, y, imgW, imgH);
}
} else {
// Image is > canvas size
// Reset, start from beginning
if (x > canvasXSize) {
x = canvasXSize - imgW;
}
// Draw additional image
if (x > canvasXSize - imgW) {
ctx.drawImage(img, x - imgW + 1, y, imgW, imgH);
}
}
// Draw image
ctx.drawImage(img, x, y, imgW, imgH);
// Amount to move
x += dx;
}
// Draw additional image if (x > canvasXSize - imgW) {ctx.drawImage(img, x - imgW + 1, y, imgW, imgH);} 绘制图片的一部分,为了保证能够无缝衔接,所以将移动的部分添加到负值然后进行补偿缺少的部分。
绘制跟随鼠标运动的动画
var cn;
//= document.getElementById('cw');
var c;
var u = 10;
const m = {
x: innerWidth / 2,
y: innerHeight / 2,
};
window.onmousemove = function (e) {
m.x = e.clientX;
m.y = e.clientY;
};
function gc() {
var s = "0123456789ABCDEF";
var c = "#";
for (var i = 0; i < 6; i++) {
c += s[Math.ceil(Math.random() * 15)];
}
return c;
}
var a = [];
window.onload = function myfunction() {
cn = document.getElementById("cw");
c = cn.getContext("2d");
for (var i = 0; i < 10; i++) {
var r = 30;
var x = Math.random() * (innerWidth - 2 * r) + r;
var y = Math.random() * (innerHeight - 2 * r) + r;
var t = new ob(
innerWidth / 2,
innerHeight / 2,
5,
"red",
Math.random() * 200 + 20,
2,
);
a.push(t);
}
//cn.style.backgroundColor = "#700bc8";
c.lineWidth = "2";
c.globalAlpha = 0.5;
resize();
anim();
};
window.onresize = function () {
resize();
};
function resize() {
cn.height = innerHeight;
cn.width = innerWidth;
for (var i = 0; i < 101; i++) {
var r = 30;
var x = Math.random() * (innerWidth - 2 * r) + r;
var y = Math.random() * (innerHeight - 2 * r) + r;
a[i] = new ob(
innerWidth / 2,
innerHeight / 2,
4,
gc(),
Math.random() * 200 + 20,
0.02,
);
}
// a[0] = new ob(innerWidth / 2, innerHeight / 2, 40, "red", 0.05, 0.05);
//a[0].dr();
}
function ob(x, y, r, cc, o, s) {
this.x = x;
this.y = y;
this.r = r;
this.cc = cc;
this.theta = Math.random() * Math.PI * 2;
this.s = s;
this.o = o;
this.t = Math.random() * 150;
this.o = o;
this.dr = function () {
const ls = {
x: this.x,
y: this.y,
};
this.theta += this.s;
this.x = m.x + Math.cos(this.theta) * this.t;
this.y = m.y + Math.sin(this.theta) * this.t;
c.beginPath();
c.lineWidth = this.r;
c.strokeStyle = this.cc;
c.moveTo(ls.x, ls.y);
c.lineTo(this.x, this.y);
c.stroke();
c.closePath();
};
}
function anim() {
requestAnimationFrame(anim);
c.fillStyle = "rgba(0,0,0,0.05)";
c.fillRect(0, 0, cn.width, cn.height);
a.forEach(function (e, i) {
e.dr();
});
}
每个小圆移动路径通过坐标变化组成多个点,利用三角函数实现围绕圆形的坐标变化,给人以沿着一个旋转轨迹移动的感觉。
this.x = m.x + Math.cos(this.theta) * this.t;
this.y = m.y + Math.sin(this.theta) * this.t;
每次更新小圆的位置,可以根据角度计算出相对鼠标位置的坐标点,如此我们小圆就会围绕鼠标中心按角度变换的方式运动。
这里的lineTo就是绘制当前位置和上一个直线,随着角度变换和小圆位置更新,可以绘制出圆形的轨迹。
- 每个小圆的运动轨迹基于 theta 角度的变化。随时间不断变化(this.theta += this.s),使得每个小圆按顺时针或逆时针旋转。
- 类似旋转的运动路径是在在画布上不断绘制线条,因此看起来就像是多个小圆围绕鼠标旋转,并绘制出旋转的轨迹。
- 最后添加带有动画背景色且带有透明度,故每一帧的运动路径形成一个渐变消失的效果,展现出旋转拖尾的效果。
写在最后
未完待续…
希望大家好好生活,健康快乐。