主要是读书笔记,里面物理知识都是通用,记录一下,里面的示例做小游戏时会经常遇到很实用。
1Canvas元素
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script type="text/javascript">
window.onload = function () {
//1、获取canvas对象
var cnv = document.getElementById("canvas");
//2、获取上下文环境对象context
var cxt = cnv.getContext("2d");
//3、开始绘制图形
cxt.moveTo(50, 100);
cxt.lineTo(150, 50);
cxt.stroke();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px dashed
gray; "></canvas>
</body>
</html>
注意:width和height分别定义Canvas的宽度和高度。默认情况下,Canvas的宽度为300px,高度为150px。(css 设置无效)
2 直线
2.1 画直线
cxt.moveTo(x1, y1);
cxt.lineTo(x2, y2);
cxt.stroke();
2.2 描边
cxt.strokeStyle = 属性值;
cxt.strokeRect(x, y, width, height);
strokeStyle属性 取值有三种,即颜色值、渐变色、图案。
strokeRect()方法 用于确定矩形的坐标,其中x和y为矩形最左上角的坐标。
2.3 填充
cxt.fillStyle=属性值;
cxt.fillRect(x, y, width, height);
fillStyle属性跟strokeStyle属性一样,取值也有三种,即颜色值、渐变色、图案。fillRect()方法跟strokeRect()方法一样,用于确定矩形的坐标,其中x和y为矩形最左上角的坐标,width表示矩形的宽度,height表示矩形的高度。跟描边矩形一样,填充矩形的fillStyle属性也必须在fillRect()方法之前定义,否则fillStyle属性无效。
2.4 rect() 方法
2.5 清空矩形
cxt.clearRect(x, y, width, height);
3. 曲线
图形、弧线、二次贝塞尔曲线、三次贝塞尔曲线。
3.1 圆
cxt.beginPath();
cxt.arc(x, y,半径,开始角度,结束角度,anticlockwise); //anticlockwise:默认false 顺时针
cxt.closePath();、三次贝塞尔曲线。
上面这个语法仅仅是对圆形的一个“状态描述”,我们还需要对圆形进行“描边”或“填充”,才会有实际效果。这一点跟绘制矩形是一样的道理,大家别忘了对比一下
//状态描述
cxt.beginPath();
cxt.arc(x, y,半径,开始角度,结束角度,anticlockwise);
cxt.closePath();
//描边
cxt.strokeStyle = "颜色值";
cxt.stroke();
//状态描述
cxt.beginPath();
cxt.arc(x, y,半径,开始角度,结束角度,anticlockwise);
cxt.closePath();
//填充
cxt.fillStyle = "颜色值";
cxt.fill();
3.2 圆弧
arc()画弧线不使用closePath()来关闭路径。这一点大家一定要区分开,因为弧线不是一个闭合图形。而closePath()是用来绘制“封闭图形”的(作用在于关闭路径、连接起点与终点)
-
cxt.arcTo(cx, cy, x2, y2, radius);
3.3 二次贝塞尔曲线
cxt.quadraticCurveTo(cx , cy , x2, y2);
3.4 三次贝塞尔曲线
cxt.bezierCurveTo(cx1 , cy1 , cx2 , cy2 , x , y);
4 线条操作
4.1 lineWidth属性
lineWidth属性:取值为整数,默认值为1,默认单位为px。
lineWidth属性不仅可以用于直线图形,也可以用于曲线图形。此外需要注意的是,假设线条宽度为lineWidth,则strokeRect()方法绘制的矩形实际宽度是(width+lineWidth),实际高度是(height+lineWidth)。而arc()方法绘制的圆形实际半径为(radius+lineWidth)。
4.2 lineCap属性
注意,round和square值会使线条稍微变长一点,因为它们给线条增加了线帽部分。
我们必须使用beginPath()来开始新的路径,才可以使用一个新的lineCap属性值。此外还需要注意,round和square值会使线条稍微变长一点,因为它们给线条增加了线帽部分。
4.3 LineJoin属性
cxt.lineJoin = "属性值";
4.4 setLineDash()方法
setLineDash()方法
5. 文本操作
5.1strokeText()方法
strokeText(text , x , y , maxWidth)
参数maxWidth为可选参数,表示允许的最大文本的宽度(单位为px)。如果文本宽度超出maxWidth值,文本会被压缩至maxWidth值的宽度。
5.2 fillText()方法
fillText(text , x , y , maxWidth)
5.3 measureText()方法
var length = cxt.measureText(text).width;
参数text表示一个字符串文本,measureText().width返回文本的长度,单位为px。
5.4 textBaseline属性
6. 图片操作
6.1 绘制图片
cxt.drawImage(image , dx , dy);
参数image,表示页面中的图片。该图片可以是页面中的img元素,也可以是JavaScript创建的Image对象
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//创建image对象
var image = new Image();
image.src = "images/princess.png";
//必须在图片载入完成后才能将图片绘制到Canvas上
image.onload = function () {
cxt.drawImage(image, 40, 20);
}
}
2. cxt.drawImage(image , dx , dy , dw , dh)
-
cxt.drawImage(image , sx , sy , sw, sh , dx , dy , dw , dh)
6.2 平铺照片
var pattern = cxt.createPattern(image , type);
cxt.fillStyle = pattern;
cxt.fillRect();
6.3 切割图片
cxt.clip();
想要使用clip()方法切割一张图片,一般需要以下三步。
(1)绘制基本图形。(2)使用clip()方法。(3)绘制图片。
//第1步,绘制基本图形,用来切割
cxt.beginPath();
cxt.arc(70, 70, 50, 0, 360 * Math.PI / 180, true);
cxt.closePath();
cxt.stroke();
//第2步,使用clip()方法,使得切割区域为上面绘制的基本图形
cxt.clip();
//第3步,绘制一张图片
var image = new Image();
image.src = "images/princess.png";
image.onload = function () {
cxt.drawImage(image, 10, 20);
}
7 变形操作
7.1 平移
cxt.translate(x, y); //“状态”都必须在“动作”之前定义
7.2 清空画布
cxt.clearRect(0,0, cnv.width, cnv.height); //等价 cxt.clearRect();
7.3 缩放
cxt.scale(x, y);
scale()方法会改变图形的以下几点。(1)左上角坐标。(2)宽度或高度。(3)线条宽度。
7.4 图形旋转
cxt.rotate(angle); //“度数*Math.PI/180”
默认情况下,rotate()方法的旋转中心就是原点坐标(0,0)
-
改变旋转中心: 如果我们想要以某一点(x, y)为旋转中心,可以先使用translate(x, y),然后再使用rotate()方法。(配合save restore方法)
cxt.clearRect(0, 0, cnv.width, cnv.height); cxt.save(); cxt.translate(cnv.width / 2, cnv.height / 2); //将坐标移 动到中心 cxt.rotate(Math.PI * (i / 100)); //累进旋转 cxt.fillStyle = "HotPink"; cxt.fillRect(-rectWidth / 2, -rectHeight / 2, rectWidth, rectHeight); //填充矩形 cxt.restore();
7.5 transform()
cxt.transform(a, b, c, d, e, f);
7.6 setTransform()方法
setTransform()方法会重置图形的状态,然后再进行变换
变形操作除了可以用于图形,还可以用于文字和图片
8 像素操作
8.1 getImageData()方法
var imgData = cxt.getImageData(x , y , width , height);
var data = imgData.data;
说明:x、y表示所选图片区域的坐标,width、height表示所选图片区域的宽度和高度。getImageData()方法返回的是一个canvasPixelArray对象,我们可以用一个变量(如imgData)来保存这个对象。canvasPixelArray对象有一个data属性,这个data属性是一个保存了这一张图片像素数据的数组,数组取值如[r1, g1, b1, a1, r2,g2, b2, a2, ……]。也就是说,在imgData.data这个数组中每四个数存储着1个像素的rgba颜色值,这四个数分别是该像素的红(r)、绿(g)、蓝(b)、透明度(a)
8.2 putImageData()方法
cxt.putImageData(image , x , y);
说明:image表示重新绘制的图形,也就是使用getImageData()方法获取的canvasPixelArray对象。x、y表示重新绘制图形左上角的横坐标、纵坐标。getImageData()和putImageData()都是配合使用的,一般我们都是先用getImageData()获取像素数据,然后利用一定的算法进行像素操作,最后再使用putImageData()方法输出像素数据(即在Canvas中显示一张图片)。上面只是简单介绍了一下getImageData()和putImageData()这两个方法的语法,在后面这几节里,我们结合相应例子并经常回过头来看看这一节,就很容易理解了
[图片上传失败...(image-5f588d-1677137124942)]
- 颜色反转
for (var i = 0; i < data.length; i += 4)
{
data[i + 0] = 255- data[i + 0];
data[i + 1] = 255- data[i + 1];
data[i + 2] = 255- data[i + 2];
}
-
黑白效果
for (var i = 0; i < data.length; i += 4) { //var average = (data[i + 0] + data[i + 1] + data[i + 2] + data[i + 3]) / 3; var grayscale = data[i] * 0.3 + data[i + 1] * 0.6 + data[i + 2] * 0.1; data[i + 0] = average; //红 data[i + 1] = average; //绿 data[i + 2] = average; //蓝 }
-
亮度效果
for (var i = 0; i < data.length; i += 4) { var a = 50; data[i + 0] += a; data[i + 1] += a; data[i + 2] += a; }
-
复古效果
for (var i = 0; i < data.length; i += 4) { var r = data[i + 0]; var g = data[i + 1]; var b = data[i + 2]; data[i + 0] = r * 0.39 + g * 0.76 + b * 0.18; data[i + 1] = r * 0.35 + g * 0.68 + b * 0.16; data[i + 2] = r * 0.27 + g * 0.53 + b * 0.13; }
-
红色蒙版
for (var i = 0; i < data.length; i += 4) { var r = data[i + 0]; var g = data[i + 1]; var b = data[i + 2]; var average = (r + g + b) / 3; data[i + 0] = average; data[i + 1] = 0; data[i + 2] = 0; }
-
透明处理
for (var i = 0; i < data.length; i += 4) { data[i + 3] = data[i + 3]*n; }
8.3 createImageData()方法
cxt.createImageData(sw, sh); //第1种格式
cxt.createImageData(imageData); //第2种格式
(1)第一种格式:接收两个参数,sw和sh分别表示要创建区域的宽度和高度。
(2)第二种格式:接收一个像素对象,表示要“创建区域”的宽度和高度与“这个像素对象”的宽度和高度相等。
createImageData(sw, sh) 和createImageData(imageData)这两个方法说白了都是用来创建一个区域而已。只不过一个是我们自己定宽高,另外一个是“复制”别人的宽高。
9. 渐变与阴影
9.1 线性渐变
var gnt = cxt.createLinearGradient(x1, y1, x2, y2);
gnt.addColorStop(value1, color1);
gnt.addColorStop(value2, color2);
cxt.fillStyle = gnt;
cxt.fill();
参数value表示渐变位置的偏移量,取值为0~1之间任意值。value1,表示渐变开始位置,value2表示渐变结束位置。
9.2 径向渐变
var gnt = cxt.createRadialGradient(x1, y1, r1, x2, y2, r2);
gnt.addColorStop(value1, color1);
gnt.addColorStop(value2, color2);
cxt.fillStyle = gnt;
cxt.fill();
(1)调用createLinearGradient()方法创建一个radialGradient对象,并赋值给变量gnt。
(2)调用radialGradient对象(即gnt)的addColorStop()方法N次,第1次表示渐变开始的颜色,第2次表示渐变结束时的颜色。然后第3次则以第2次渐变颜色作为开始颜色,进行渐变,以此类推。
(3)把radialGradient对象(即gnt)赋值给fillStyle属性,并且调用fill()方法来绘制有渐变色的图形。(x1 , y1)表示渐变开始圆心的坐标,r1表示渐变开始圆心的半径。(x2 , y2)表示渐变结束圆心的坐标,r2表示渐变结束圆的半径。
9.3 阴影
//定义绘制图形的函数
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//设置右下方向的阴影
cxt.shadowOffsetX = 5;
cxt.shadowOffsetY = 5;
cxt.shadowColor = "LightSkyBlue ";
cxt.shadowBlur = 10;
cxt.fillStyle = "HotPink";
cxt.fillRect(100, 30, 50, 50);
}
10 Canvas路径
10.1 beginPath()
Canvas是基于“状态”来绘制图形的。每一次绘制(stroke()或fill()), Canvas会检测整个程序定义的所有状态,这些状态包括strokeStyle、fillStyle、lineWidth等。当一个状态值没有被改变时,Canvas就一直使用最初的值。当一个状态值被改变时,需要分两种情况考虑。
(1)如果使用beginPath()开始一个新的路径,则不同路径使用不同的值。
(2)如果没有使用beginPath()开始一个新的路径,则后面的值会覆盖前面的值(后来者居上原则)。
//第1条直线
cxt.beginPath();
cxt.moveTo(50, 40);
cxt.lineTo(150, 40);
cxt.strokeStyle = "red";
cxt.stroke();
//第2条直线
cxt.beginPath();// 注意:这里开启一个新的路径
cxt.moveTo(50, 80);
cxt.lineTo(150, 80);
cxt.strokeStyle = "green";
cxt.stroke();
10.2 closePath()
“关闭路径”并不等于“结束路径”,所谓的“关闭路径”一般是指将同一个路径的起点与终点这两点连接起来,使其成为一个封闭的图形。
10.3 isPointInPath()方法
cxt.isPointInPath(x , y);
注意,isPointInPath()不支持Canvas自带的两个方法:strokeRect()、fillRect()。如果想要使用strokeRect()和fillRect(),请使用rect()方法来代替。
11 Canvas 状态
Canvas为我们提供了两个操作状态的方法:save()和restore()。在Canvas中,我们可以使用save()方法来保存“当前状态”,然后可以使用restore()方法来恢复“之前保存的状态”。save()和restore()一般情况下都是成对配合使用的。
11.1 clip()方法
cxt.clip();
我们在使用clip()方法之前,必须要在Canvas中绘制一个基本图形。然后调用clip()方法后,这个基本图形就会变为一个剪切区域。
注意,与之前接触的isPointInPath()方法一样,clip()方法也不支持Canvas自带的两个方法:strokeRect()、fillRect()。如果要使用strokeRect()和fillRect(),请使用rect()方法来代替。
11.1 使用场景
Canvas状态的保存和恢复,主要用于以下三种场合。
(1)图形或图片裁切。
(2)图形或图片变形。
(3)以下属性改变的时候:fillStyle、font、globalAlpha、globalCompositeOperation、lineCap、lineJoin、lineWidth、miterLimit、shadowBlur、shadowColor、shadowOffsetX、shadowOffsetY、strokeStyle、textAlign、textBaseline。
cxt.save();
cxt.fillStyle = "HotPink";
cxt.translate(30, 30);
cxt.fillRect(0, 0, 100, 50);
cxt.restore();//恢复 后面的平移坐标系会基于原点位置
cxt.fillStyle = "LightSkyBlue ";
cxt.translate(60, 60);
cxt.fillRect(0, 0, 100, 50);
在变形操作(平移、缩放、旋转)中,我们一般都是在变形操作之前使用save()方法保存当前状态,其中当前状态包括参考坐标、图形大小等。然后再使用restore()方法来恢复之前保存的状态。save( )和restore( ),在变形操作中会大量用到。
12 其他应用
12.1 Canvas对象属性
使用cnv.width和cnv.height来获取Canvas的宽度和高度是经常被用到的,像文本居中时需要计算Canvas的宽度和高度、clearRect()清空Canvas时也需要计算Canvas的宽度和高度。
12.1 Canvas对象方法
cnv.toDataURL(type);
参数type是可选参数,表示输出的MIME类型。如果参数type省略,将使用image/png类型
12.2 globalAlpha属性
context.globalAlpha = 数值;
globalAlpha默认值为1.0(完全不透明),取值范围为:0.0~1.0。其中0.0表示完全透明,1.0表示完全不透明。globalAlpha属性必须在图形绘制之前定义才有效。注意,定义globalAlpha之后,会对整个画布都起作用,因此我们在定义时要多加小心。
13 Canvas事件
在Canvas中,鼠标事件分为以下三种。(1)鼠标按下:mousedown(2)鼠标松开:mouseup(3)鼠标移动:mousemove
13.1 获取鼠标的位置封装工具
//将tools定义为window对象的属性,该属性的值是一个对象
window.tools = {};
//获取鼠标位置
window.tools.getMouse = function (element) {
//定义一个mouse的对象
var mouse = { x: 0, y: 0 };
//为传入的元素添加mousemove事件
addEvent(element, "mousemove", function (e) {
var x, y;
//在IE中,event对象是作为window对象的一个属性存在
var e = e || window.event;
//获取鼠标当前位置,并作兼容处理
//兼容Firefox、chrome、IE9及以上
if (e.pageX || e.pageY) {
x = e.pageX;
y = e.pageY;
}
//兼容IE8及以下,以及混杂模式下的Chrome和Safari
else {
x = e.clientX + document.body.scrollLeft || document.
documentElement.scrollLeft;
y = e.clientY + document.body.scrollTop || document.
documentElement.scrollTop;
}
//将当前的坐标值减去canvas元素的偏移位置,则x、y为鼠标在canvas中的相对坐标
x -= element.offsetLeft;
y -= element.offsetTop;
mouse.x = x;
mouse.y = y;
})
//返回值为mouse对象
return mouse;
}
13.2 获取键盘方向
//获取键盘控制方向
window.tools.getKey = function () {
var key = {};
window.addEventListener("keydown", function (e) {
if (e.keyCode == 38 || e.keyCode == 87) {
key.direction = "up";
} else if (e.keyCode == 39 || e.keyCode == 68) {
key.direction = "right";
} else if (e.keyCode == 40 || e.keyCode == 83) {
key.direction = "down";
} else if (e.keyCode == 37 || e.keyCode == 65) {
key.direction = "left";
} else {
key.direction = "";
}
}, false);
return key;
}
13.3 循环事件
在Canvas中,我们都是使用requestAnimationFrame()方法来实现循环,从而达到动画效果。
(function frame(){
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
……
})();
小球向右移动动画
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化变量,也就是初始化圆的x轴坐标为0
var x = 0;
//动画循环
(function frame() {
window.requestAnimationFrame(frame);
//每次动画循环都先清空画布,再重绘新的图形
cxt.clearRect(0, 0, cnv.width, cnv.height);
//绘制圆
cxt.beginPath();
cxt.arc(x, 70, 20, 0, 360 * Math.PI / 180, true);
cxt.closePath();
cxt.fillStyle = "#6699FF";
cxt.fill();
//变量递增
x += 2;
})();
14 物理动画
在Canvas中我们可以使用反正切函数Math.atan2()来求出两条边之间夹角的度数,并且能够准确判断该度数对应的是哪一个夹角。
14.1 跟随鼠标旋转
function Arrow(x, y, color, angle)
{
//箭头中心x坐标,默认值为0
this.x = x || 0;
//箭头中心y坐标,默认值为0
this.y = y || 0;
//颜色,默认值为"#FF0099"
this.color = color || "#FF0099";
//旋转角度,默认值为0
this.angle = angle || 0;
}
Arrow.prototype = {
stroke: function (cxt) {
cxt.save();
cxt.translate(this.x, this.y);
cxt.rotate(this.angle);
cxt.strokeStyle = this.color;
cxt.beginPath();
cxt.moveTo(-20, -10);
cxt.lineTo(0, -10);
cxt.lineTo(0, -20);
cxt.lineTo(20, 0);
cxt.lineTo(0, 20);
cxt.lineTo(0, 10);
cxt.lineTo(-20, 10);
cxt.closePath();
cxt.stroke();
cxt.restore();
},
fill: function (cxt) {
cxt.save();
cxt.translate(this.x, this.y);
cxt.rotate(this.angle);
cxt.fillStyle = this.color;
cxt.beginPath();
cxt.moveTo(-20, -10);
cxt.lineTo(0, -10);
cxt.lineTo(0, -20);
cxt.lineTo(20, 0);
cxt.lineTo(0, 20);
cxt.lineTo(0, 10);
cxt.lineTo(-20, 10);
cxt.closePath();
cxt.fill();
cxt.restore();
}
};
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/arrow.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//实例化一个箭头,中点坐标为画布中心坐标
var arrow = new Arrow(cnv.width / 2, cnv.height / 2);
//获取鼠标坐标
var mouse = tools.getMouse(cnv);
(function drawFrame() {
window.requestAnimationFrame(drawFrame, cnv);
cxt.clearRect(0, 0, cnv.width, cnv.height);
var dx = mouse.x - cnv.width / 2;
var dy = mouse.y - cnv.height / 2;
//使用Math.atan2()方法计算出鼠标与箭头中心的夹角
arrow.angle = Math.atan2(dy, dx);
arrow.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid
silver; "></canvas>
</body>
</html>
14.2 圆周运动
“圆的标准方程”经过数学推理可以得到圆上任意一点的坐标。
x = centerX + Math.cos(angle)*radius;
y = centerY + Math.sin(angle)*radius;
//再建立一个ball.js文件,用于存放一个小球类
function Ball(x, y, radius, color)
{
//小球中心的x坐标,默认值为0
this.x = x || 0;
//小球中心的y坐标,默认值为0
this.y = y || 0;
//小球半径,默认值为12
this.radius = radius || 12;
//小球颜色,默认值为"#6699FF"
this.color = color || "#6699FF";
this.scaleX = 1;
this.scaleY = 1;
}
Ball.prototype = {
//绘制"描边"小球
stroke: function (cxt) {
cxt.save();
cxt.scale(this.scaleX, this.scaleY);
cxt.strokeStyle = this.color;
cxt.beginPath();
cxt.arc(this.x, this.y, this.radius, 0, 360 * Math.PI / 180, false);
cxt.closePath();
cxt.stroke();
cxt.restore();
},
//绘制"填充"小球
fill: function (cxt) {
cxt.save();
cxt.translate(this.x, this.y);
cxt.rotate(this.rotation);
cxt.scale(this.scaleX, this.scaleY);
cxt.fillStyle = this.color;
cxt.beginPath();
cxt.arc(0, 0, this.radius, 0, 360 * Math.PI / 180, false);
cxt.closePath();
cxt.fill();
cxt.restore();
}
}
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//实例化一个小球,中心坐标为(100,25),半径、颜色都取默认值
var ball = new Ball(100, 25);
var centerX = cnv.width / 2;
var centerY = cnv.height / 2;
var radius = 50;
var angle = 0;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
//绘制圆形
cxt.beginPath();
cxt.arc(centerX, centerY, 50, 0, 360 * Math.PI / 180, false);
cxt.closePath();
cxt.stroke();
//计算小球坐标
ball.x = centerX + Math.cos(angle) * radius;
ball.y = centerY + Math.sin(angle) * radius;
ball.fill(cxt);
//角度递增
angle += 0.05;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid
silver; "></canvas>
</body>
</html>
14.2 波形运动
常见的波形运动可以分为三种
(1)作用于x轴坐标。
(2)作用于y轴坐标。
(3)作用于缩放属性(scaleX或scaleY)。
(1) 作用于x轴坐标。
x = centerX + Math.sin(angle) * range;
angle += speed;
(2)作用于y轴坐标。
y = centerY + Math.sin(angle) * range;
angle += speed;
(3) 作用于缩放属性(scaleX或scaleY)。
当正弦函数sin作用于物体的缩放属性(scaleX或scaleY)时,物体会不断地放大然后缩小,从而产生一种脉冲动画的效果。
var ball = new Ball(cnv.width / 2, cnv.height / 2, 25);
var range = 0.5;
var angle = 0;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.scaleX = 1 + Math.sin(angle) * range;
ball.scaleY = 1 + Math.sin(angle) * range;
ball.fill(cxt);
//角度递增
angle += 0.05;
})();
14.3 匀速运动
小球沿着任意方向进行匀速运动
vx = speed * Math.cos(angle * Math.PI/180);
vy = speed * Math.sin(angle * Math.PI/180);
object.x += vx;
object.y += vy;
14.4 加速运动
小球慢慢减速反向
//实例化一个小球
var ball = new Ball(0, cnv.height / 2);
//初始化x轴速度以及加速度
var vx = 8;
var ax = -0.2;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.x += vx;
ball.fill(cxt);
vx += ax;
})();
ax = a * Math.cos(angle * Math.PI/180);
ay = a * Math.sin(angle * Math.PI/180);
vx += ax;
vy += ay;
object.x += vx;
object.y += vy;
14.5 重力
vy += gravity;
object.y += vy;
小球从空中自由降落到地面,然后反弹,循环往复,直到它最终速度为0而停止在地面上。
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化数据
var ball = new Ball(cnv.width / 2, 0);
//y轴初始速度为0,重力加速度为0.2,反弹系数为-0.8
var vy = 0;
var gravity = 0.2;
var bounce = -0.8;
(function drawFrame() {
window.requestAnimationFrame(drawFrame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.y += vy;
//边界检测
if (ball.y > cnv.height - ball.radius) {
ball.y = cnv.height - ball.radius;
//速度反向并且减小
vy = vy * bounce;
}
ball.fill(cxt);
vy += gravity;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid
silver; "></canvas>
</body>
</html>
14.6 摩擦力
摩擦力只会改变速度的大小而不会改变它的方向。换句话说,摩擦力只能将物体的速度降至0,但它无法让物体掉头往相反的方向移动。
vx *= friction;
vy *= friction;
object.x += vx;
object.y += vy;
15 边界检测
对于边界检测,将从以下四个方面来介绍。(1)边界限制(2)边界生成(3)边界环绕(4)边界反弹
15.1 边界限制
if (ball.x < ball.radius) {
//小球"碰到"左边界时
} else if (ball.x > cnv.width - ball.radius) {
//小球"碰到"右边界时
}
if (ball.y < ball.radius) {
//小球"碰到"上边界时
} else if (ball.y > cnv.height - ball.radius) {
//小球"碰到"下边界时
}
15.2 边界环绕
指的是当物体从一个边界消失后,它就会从对立的边界重新出现,从而形成一种环绕效果
if(ball.x < -ball.radius){
//小球"完全超出"左边界时
} else if(ball.x>cnv.width + ball.radius){
//小球"完全超出"右边界时
}
if(ball.y<-ball.radius){
//小球"完全超出"上边界时
} else if(ball.y>cnv.height + ball.radius){
//小球"完全超出"下边界时
}
15.3 边界生成
边界生成,指的是物体完全超出边界之后,会在最开始的位置重新生成。
if (ball.x < -ball.radius ||
ball.x > cnv.width + ball.radius ||
ball.y < -ball.radius ||
ball.y > cnv.height + ball.radius) {
……
}
15.4 边界反弹
指的是物体触碰到边界之后就会反弹回来,就像现实世界中小球碰到墙壁反弹一样。
//碰到左边界
if (ball.x < ball.radius) {
ball.x = ball.radius;
vx = -vx;
//碰到右边界
} else if (ball.x > canvas.width - ball.radius) {
ball.x = canvas.width - ball.radius;
vx = -vx;
}
//碰到上边界
if (ball.y < ball.radius) {
ball.y = ball.radius;
vy = -vy;
//碰到下边界
} else if (ball.y > canvas.height - ball.radius) {
ball.y = canvas.height - ball.radius;
vy = -vy;
}
16 碰撞检测
碰撞检测比较常用的是以下两种方法。(1)外接矩形判定法(2)外接圆判定法
16.1 外接矩形判定法
两个矩形左上角的坐标所处的范围。如果两个矩形左上角的坐标满足一定条件,则两个矩形就发生了碰撞。
window.tools.checkRect = function (rectA, rectB) {
return ! (rectA.x + rectA.width < rectB.x ||
rectB.x + rectB.width < rectA.x ||
rectA.y + rectA.height < rectB.y ||
rectB.y + rectB.height < rectA.y);
}
16.2 外接圆判定法
两个圆心之间的距离。如果两个圆心之间的距离大于或等于两个圆的半径之和,则两个圆没有发生碰撞;如果两个圆心之间的距离小于两个圆的半径之和,则两个圆发生了碰撞。
window.tools.checkCircle = function (circleB, circleA) {
var dx = circleB.x - circleA.x;
var dy = circleB.y - circleA.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < (circleA.radius + circleB.radius)) {
return true;
}
else {
return false;
}
}
17 用户交互
用户交互的效果(1)捕获物体(2)拖曳物体(3)抛掷物体
17.1 捕获物体
(1)矩形的捕获。(2)圆的捕获。
-
矩形的捕获
if (mouse.x > rect.x && mouse.x < rect.x + rect.width && mouse.y > rect.y && mouse.y < rect.y + rect.height) { …… }
-
元的捕获
dx = mouse.x - ball.x; dy = mouse.y - ball.y; distance = Math.sqrt(dx*dx + dy*dy); if(distance < ball.radius){ …… }
17.2 拖拽物体
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化数据
var ball = new Ball(cnv.width / 2, cnv.height / 2, 20);
ball.fill(cxt);
var mouse = tools.getMouse(cnv);
//初始化2个变量:dx和dy
var dx = 0, dy = 0;
cnv.addEventListener("mousedown", function () {
if (ball.checkMouse(mouse)) {
//dx为鼠标与球心的水平偏移量
dx = mouse.x - ball.x;
//dy为鼠标与球心的垂直偏移量
dy = mouse.y - ball.y;
document.addEventListener("mousemove", onMouseMove, false);
document.addEventListener("mouseup", onMouseUp, false);
}
}, false);
function onMouseMove() {
//更新小球坐标
ball.x = mouse.x - dx;
ball.y = mouse.y - dy;
}
function onMouseUp() {
//鼠标松开时,移除鼠标松开事件:mouseup(自身事件)
document.removeEventListener("mouseup", onMouseUp, false);
//鼠标松开时,移除鼠标移动事件:mousemove
document.removeEventListener("mousemove", onMouseMove, false);
}
(function drawFrame() {
window.requestAnimationFrame(drawFrame, cnv);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid
silver; "></canvas>
</body>
</html>
重力反弹
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var ball = new Ball(cnv.width / 2, cnv.height, 20);
ball.fill(cxt);
var mouse = tools.getMouse(cnv);
var isMouseDown = false;
var dx = 0, dy = 0;
//oldX和oldY用于存储小球旧的坐标
var oldX, oldY;
//初始速度vx和vy都为0
var vx = 0, vy = 0;
//加入重力和反弹消耗
var gravity = 1.5;
var bounce = -0.8;
cnv.addEventListener("mousedown", function () {
//判断鼠标点击是否落在小球上
if (ball.checkMouse(mouse)) {
//鼠标按下小球时,isMouseDown设置为true
isMouseDown = true;
//鼠标按下小球时,将当前鼠标位置赋值给oldX和oldY
oldX = ball.x;
oldY = ball.y;
dx = mouse.x - ball.x;
dy = mouse.y - ball.y;
document.addEventListener("mousemove", onMouseMove, false);
document.addEventListener("mouseup", onMouseUp, false);
}
}, false);
function onMouseMove() {
//鼠标移动时,更新小球坐标
ball.x = mouse.x - dx;
ball.y = mouse.y - dy;
//加入边界限制
//当小球碰到左边界时
if (ball.x < ball.radius) {
ball.x = ball.radius;
//当小球碰到右边界时
} else if (ball.x > cnv.width - ball.radius) {
ball.x = cnv.width - ball.radius;
}
//当小球碰到上边界时
if (ball.y < ball.radius) {
ball.y = ball.radius;
//当小球碰到下边界时
} else if (ball.y > cnv.height - ball.radius) {
ball.y = cnv.height - ball.radius;
}
}
function onMouseUp() {
//鼠标松开时,isMouseDown设置为false
isMouseDown = false;
document.removeEventListener("mouseup", onMouseUp, false);
document.removeEventListener("mousemove", onMouseMove, false);
}
(function drawFrame() {
window.requestAnimationFrame(drawFrame, cnv);
cxt.clearRect(0, 0, cnv.width, cnv.height);
if (isMouseDown) {
//如果isMouseDown为true,用当前小球的位置减去上一帧的坐标
vx = ball.x - oldX;
vy = ball.y - oldY;
//如果isMouseDown为true,更新oldX和oldY为当前小球中心坐标
oldX = ball.x;
oldY = ball.y;
} else {
//如果isMouseDown为false,小球沿着抛掷方向运动
vy += gravity;
ball.x += vx;
ball.y += vy;
//边界检测
//碰到右边界
if (ball.x > canvas.width - ball.radius) {
ball.x = canvas.width - ball.radius;
vx = vx * bounce;
//碰到左边界
} else if (ball.x < ball.radius) {
ball.x = ball.radius;
vx = vx * bounce;
}
//碰到下边界
if (ball.y > canvas.height - ball.radius) {
ball.y = canvas.height - ball.radius;
vy = vy * bounce;
//碰到上边界
} else if (ball.y < ball.radius) {
ball.y = ball.radius;
vy = vy * bounce;
}
}
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="300" height="200" style="border:1px solid
silver; "></canvas>
</body>
</html>
18 高级动画
18.1 缓动动画
想要实现缓动动画,一般需要以下五个步骤。
(1)定义一个0~1之间的缓动系数easing。
(2)计算出物体与终点之间的距离。
(3)计算出当前速度,其中当前速度=距离×缓动系数。
(4)计算新的位置,其中新的位置=当前位置+当前速度。
(5)重复执行第2~4步,直到物体达到目标。
var targetX = 任意位置;
var targetY = 任意位置;
//动画循环
var vx = (targetX - object.x) * easing;
var vy = (targetY- object.y) * easing;
任意方向的缓动动画
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var ball = new Ball(0, 0);
//定义终点的x轴坐标和y轴坐标
var targetX = cnv.width * (3 / 4);
var targetY = cnv.height * (1 / 2);
//定义缓动系数
var easing = 0.05;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
var vx = (targetX - ball.x) * easing;
var vy = (targetY - ball.y) * easing;
ball.x += vx;
ball.y += vy;
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid
silver; "></canvas>
</body>
</html>
18.2 缓动动画应用
缓动动画不仅可以用于物体的运动,还可以应用于物体的其他各种属性,包括大小、颜色、透明度以及旋转等
不管缓动动画应用于什么方面,其实现思路是一样的,也就是以下两个步骤。
(1)当前速度 =(最终值 - 当前值)×缓动系数。
(2)新的值 = 当前值 + 当前速度。
18.3 弹性动画简介
在缓动动画中,物体滑动到终点就停下来了;但是在弹性动画中,物体滑动到终点后还会来回反弹一会儿,直至停止。
从技术上来说,缓动动画和弹性动画有以下几个共同点。
(1)需要设置一个终点。
(2)需要确定物体到终点的距离。
(3)运动和距离是成正比的。
两者的不同在于“运动和距离成正比的”这一点的实现方式是不一样的。
(1)在缓动动画中,跟距离成正比的是“速度”。物体离终点越远,速度就越快。当物体接近终点时,它就几乎停下来了。
(2)在弹性动画中,跟距离成正比的是“加速度”。物体离终点越远,加速度越大。刚刚开始,由于加速度的影响,速度会快速增大。当物体接近终点时,加速度变得很小,但是它还在加速。由于加速度的影响,物体会越过终点。然后随着距离的变大,反向加速度也随之变大,就会把物体拉回来。物体在终点附近来回反弹一会儿,最终在摩擦力的作用下停止。
ax = (targetX - object.x) * spring;
ay = (targetY - object.y) * spring;
vx += ax;
vy += ay;
vx *= friction;
vy *= friction;
object.x += vx;
object.y += vy;
举例:加入摩擦力的弹性动画
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var ball = new Ball(0, cnv.height / 2);
var targetX = cnv.width / 2;
var spring = 0.02;
var vx = 0;
var friction = 0.95;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
var ax = (targetX - ball.x) * spring;
vx += ax;
vx *= friction;
ball.x += vx;
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid
silver; "></canvas>
</body>
</html>