在掘金的第一篇文章,可能不专业,也可能对你有帮助,有什么问题大家随时交流。
效果图
涉及到的知识点
贝塞尔曲线
- 线性贝塞尔曲线
线性贝塞尔曲线函数中的t会经过由P0至P1的B(t)所描述的曲线。例如当t=0.25时,B(t)即一条由点P0至P1路径的四分之一处。就像由0至1的连续t,B(t)描述一条由P0至P1的直线。
- 二次方贝塞尔曲线
为建构二次方贝塞尔曲线,可以中介点Q0和Q1作为由0至1的t:
由P0至P1的连续点Q0,描述一条线性贝塞尔曲线。 由P1至P2的连续点Q1,描述一条线性贝塞尔曲线。 由Q0至Q1的连续点B(t),描述一条二次贝塞尔曲线。
我们在这里用到的是下面这个方法。 CanvasRenderingContext2D.quadraticCurveTo()
是 Canvas 2D API
新增二次贝塞尔曲线路径的方法。它需要2个点。 第一个点是控制点,第二个点是终点。 起始点是当前路径最新的点,当创建二次贝赛尔曲线之前,可以使用 moveTo()
方法进行改变。
由此我们就可以绘制出波浪
ctx.beginPath();
var offsetY = startY - radius*2*process/100;
ctx.moveTo(startX - offset, offsetY);
ctx.fillStyle = waveColorDeep;
for (var i = 0; i < kWaveCount; i++) {
var dx = i * kWaveWidth;
var offsetX = dx + startX - offset;
ctx.quadraticCurveTo(offsetX + kWaveWidth/4, offsetY + kWaveHeight, offsetX + kWaveWidth/2, offsetY);
ctx.quadraticCurveTo(offsetX + kWaveWidth/4 + kWaveWidth/2, offsetY - kWaveHeight, offsetX + kWaveWidth, offsetY);
}
ctx.lineTo(startX + kWaveWidthAll, canvas.height);
ctx.lineTo(startX, canvas.height);
ctx.fill();
ctx.closePath();
复制代码
请求动画帧
window.requestAnimationFrame()
方法告诉浏览器您希望执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画。
这么我们就可以每次调整波浪的起始点使得产生一个波浪滚动的效果。
兼容处理
每个浏览器对requestAnimationFrame
函数的调用方法都有差别,所以再做一个简单的修正。 根据Firefox
的特性来看,其mozRequestAnimationFrame
提供的最高FPS为60,并且会根据每一帧的计算的耗时来进行调整,比如每一帧计算用了1s,那他只会提供1FPS的动画效果。 而Chrome
的高版本同样也实现了这个函数,叫webkitRequestAnimationFrame
,可以预见未来还会有Opera
的oRequestAnimationFrame
和IE
的msRequestAnimationFrame
,所以这里一并做一个简单的兼容处理:
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
复制代码
全部代码
剩下的都是一些基本操作,这里就不做过多解释,大部分代码都有注释。
ss-wave-animation.js:
var canvas = document.getElementById('wave');
var ctx = canvas.getContext('2d');
// 中点坐标
var centerX = canvas.width/2;
var centerY = canvas.height/2;
// 波浪总宽度
var kWaveWidthAll = 300;
// 波浪高度
var kWaveHeight = 10;
// 波浪个数
var kWaveCount = 4;
// 偏移量
var offset = 0;
// 遮罩半径
var radius = 90;
// 起始x
var startX = -100;
// 起始y
var startY = centerY + radius;
// 单个波浪的宽度
var kWaveWidth = kWaveWidthAll / kWaveCount;
// 将360度分成100份,那么每一份就是rad度
var rad = Math.PI*2/100;
// 加载进度
var process = 0;
// 目标进度
var targetProcess = 20;
// 波浪颜色
var waveColorDeep = "rgba(0, 0, 255, 1)";
var waveColorLight = "rgba(0, 0, 255, 0.5)";
// 遮罩颜色
var maskColor = "rgb(255, 0, 0)";
// 环形进度条颜色
var processBgColor = "rgb(0,255,0)";
var processColor = "rgb(0,0,0)";
// 文字颜色
var textColor = "rgb(0, 0, 0)";
function draw() {
offset -= 5;
if (-1 * offset === kWaveWidth) offset = 0;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 第一个波浪
ctx.beginPath();
var offsetY = startY - radius*2*process/100;
ctx.moveTo(startX - offset, offsetY);
ctx.fillStyle = waveColorDeep;
for (var i = 0; i < kWaveCount; i++) {
var dx = i * kWaveWidth;
var offsetX = dx + startX - offset;
ctx.quadraticCurveTo(offsetX + kWaveWidth/4, offsetY + kWaveHeight, offsetX + kWaveWidth/2, offsetY);
ctx.quadraticCurveTo(offsetX + kWaveWidth/4 + kWaveWidth/2, offsetY - kWaveHeight, offsetX + kWaveWidth, offsetY);
}
ctx.lineTo(startX + kWaveWidthAll, canvas.height);
ctx.lineTo(startX, canvas.height);
ctx.fill();
ctx.closePath();
// 第二个波浪
ctx.beginPath();
ctx.moveTo(startX - offset, offsetY);
ctx.fillStyle = waveColorLight;
for (var i = 0; i < kWaveCount; i++) {
var dx = i * kWaveWidth;
var offsetX = dx + startX - offset - offset;
ctx.quadraticCurveTo(offsetX + kWaveWidth/4, offsetY + kWaveHeight, offsetX + kWaveWidth/2, offsetY);
ctx.quadraticCurveTo(offsetX + kWaveWidth/4 + kWaveWidth/2, offsetY - kWaveHeight, offsetX + kWaveWidth, offsetY);
}
ctx.lineTo(startX + kWaveWidthAll, canvas.height);
ctx.lineTo(startX, canvas.height);
ctx.fill();
ctx.closePath();
// 遮罩
ctx.fillStyle = maskColor;
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.arc(centerX,centerY,radius,0,Math.PI*2,true);
ctx.fill();
ctx.closePath();
// 环形进度条不变部分
ctx.strokeStyle = processBgColor;
ctx.lineWidth = 2; //设置线宽
ctx.beginPath();
ctx.arc(centerX,centerY,radius,0,Math.PI*2,true);
ctx.stroke();
ctx.closePath();
// 环境进度条变化部分
ctx.lineWidth = 5;
ctx.strokeStyle = processColor;
ctx.beginPath();
ctx.arc(centerX,centerY,radius,-Math.PI/2,-Math.PI/2 + process*rad,false);
ctx.stroke();
ctx.closePath();
// 进度文字
ctx.beginPath();
ctx.textBaseline = 'middle'; // 设置文本的垂直对齐方式
ctx.textAlign = 'center'; // 设置文本的水平对齐方式
ctx.strokeStyle = textColor; // 设置描边样式
ctx.font = "40px Arial"; // 设置字体大小和字体
ctx.strokeText(process.toFixed(0)+"%", 100, 100); //绘制字体,并且指定位置
ctx.stroke(); //执行绘制
ctx.closePath();
if(process > targetProcess) {
// 无限循环
// process = 0;
// 关闭无限循环
cancelAnimationFrame(draw);
return;
};
process += 0.1;
requestAnimationFrame(draw);
}
draw();
复制代码
index.html:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<style type="text/css">
#wave {
background-color: white;
border: solid 1px black;
}
</style>
<title>SSWaveAnimation</title>
</head>
<body>
<canvas id="wave" width="200px" height="200px">
</canvas>
<script type="text/javascript" src="ss-wave-animation.js"></script>
</body>
</html>
复制代码
最后
详细代码和demo都在这里 github.com/sosoneo/SSW…