IIE9开始支持canvas
canvas不能用来布局,需要有一个div包裹
第一个步骤是要获取渲染上下文:getContext('2d/webgl'),参数是上下文的类型,用这个方法的时候会获取CanvasRenderingContext2D / WebGlRenderingContext的接口
图形绘制
fillStyle | strokeStyle
矩形(唯一的原生图形)
不需要ctx.beginPath()
fillRect | strokeRect(x,y,width,height)
clearRect(x,y,width,height)清除矩形
镂空的透明矩形
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
ctx.fillStyle = 'orange';
ctx.fillRect(50, 50, 100, 100);
ctx.clearRect(75, 75, 50, 50);
路径相关
beginPath开拓路径,中间都是画笔的设置,用closePath闭合路径,可以不用画最后一条线,并且闭合的也最自然,最后的stroke才是绘制图形
closePath只会闭合最后一条子路径
ctx.beginPath();
ctx.strokeStyle = 'orange';
ctx.moveTo(50, 50);
ctx.lineWidth = 10;
ctx.lineTo(150, 50);
ctx.lineTo(100, 100);
ctx.closePath();
ctx.stroke();
线段端点样式
ctx.lineCap = 'round | square(加上一段矩形) | butt(默认)',square和butt不一样,square会加上一段帽子,更长,帽子的长度是高度的一半。
ctx.lineJoin = 'miter | bevel | round';
ctx.beginPath();
ctx.moveTo(30, 200);
ctx.lineWidth = 20;
ctx.lineJoin = 'bevel';
ctx.lineTo(150, 30);
ctx.lineTo(300, 200);
ctx.stroke();
(miter默认)
(bevel斜接,类似折纸的效果)
(round)
miter length 尖角长度,miter value > miter limit限制的话,就会把超出部分裁剪掉,没超出就不会被裁剪
检查某个点是否在路径上 isPointInPath / isPointInStroke
ctx.rect(100,100,300,300);
ctx.fill();
console.log(ctx.isPointInPath(200,200));
圆
arc(x,y,radius半径,startAngle,endAngle,anticlockwise【默认顺时针false | 逆时针true】)
Math.PI 相当于180°(π),想要某个角度 Math.PI / 180 * n
ctx.beginPath();
ctx.arc(70, 70, 20, 0, Math.PI / 180 * 64, true);
ctx.stroke();
圆弧路径
arcTo(x1,y1,x2,y2,radius)
二次/三次贝塞尔曲线
quadraticCurveTo(cpx,cpy,x,y),cp - control point
ctx.beginPath();
ctx.moveTo(50, 20); //起点
ctx.quadraticCurveTo(230, 30, 50, 100);
ctx.stroke();
bazierCurveTo(cpx1,cpy1,cpx2,cpy2,x,y)
坐标轴变换
translate(x,y)平移
rotate(deg)旋转
放大:scale(x,y),不仅会缩放宽度,位置也会进行变化(单位发生变化)
ctx.scale(2, 1);
ctx.fillRect(50, 0, 50, 50);
transform(水平缩放,垂直倾斜,水平倾斜,垂直缩放,水平移动,垂直移动)
setTransform(),重新变形,之前的变形都无效,参数和transform一样,将第二行替换成transform就可以看出区别了。
ctx.translate(50, 50);
ctx.setTransform(1, 1, 0, 1, 0, 0);
ctx.fillRect(0, 0, 50, 50);
保存/还原状态
save()保存最近一次的状态、restore()读档
ctx.save();
ctx.fillRect(0, 0, 30, 30);
ctx.translate(50, 30);
ctx.fillRect(0, 0, 30, 30);
ctx.restore();
ctx.fillStyle = 'orange';
ctx.fillRect(0, 0, 30, 30);
渐变
线性渐变:createLinearGradient,addColorStop添加颜色的位置(0-1)
var gradient = ctx.createLinearGradient(0, 0, 300, 0);
gradient.addColorStop(0, 'lightblue');
gradient.addColorStop(1, 'white');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 300, 300);
径向渐变:createRadialGradient(x1,y1,radius1,x2,y2,radius2)
替换第一行即可
var gradient = ctx.createRadialGradient(100, 100, 50, 100, 100, 100);
阴影
ctx.shadowColor = '#00f';
ctx.shadowOffsetX = 15;
ctx.shadowOffsetY = 15;
ctx.shadowBlur = 15;
ctx.fillStyle = 'red';
ctx.fillRect(50, 50, 150, 100);
文本 fillText(text,x,y)
ctx.font = '40px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#424242';
ctx.fillText('Jackson', 100, 50)
透明度 globalAlpha
ctx.globalAlpha = 0.5
层级 globalCompositeOperation
ctx.globalCompositeOperation = 'source-over(默认,重叠)| source-in(交集)| destination-out(擦除)| destination-over(层级变化)'
ctx.fillStyle = 'red';
ctx.fillRect(20, 20, 100, 75);
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 100, 75);
裁剪 clip
ctx.beginPath();
ctx.arc(300, 300, 150, 0, 2*Math.PI, false);
ctx.stroke();
ctx.clip();
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 300, 300);
将画布转化为二进制编码 toDataURL
var dataURL = canvas.toDataURL();
图片相关
1. 将图案作为填充样式,createPattern
var img = new Image();
img.src = '1.jpg';
img.onload = function() {
var pattern = ctx.createPattern(img, 'no-repeat');
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 300, 150);
}
图片源不仅可以选择图片,还可以选择另一个canvas元素(用选择器选择,然后替换即可)
2. 截取放置图片:drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
var img = new Image();
img.src = '1.jpg';
img.onload = function() {
ctx.drawImage(img, 100, 100, 200, 200, 100, 100, 200, 200);
}
drawImage不仅可以用动态创建图片元素,还可以在HTML结构中获取
这个方法有很多变体,如果只传三个参数ctx.drawImage(img, dx, dy)【应用场景:当一个折线图背景,然后画线】;如果只传5个参数,会有缩放的效果ctx.drawImage(img, dx, dy,dWidth, dHeight)【应用场景:用for循环平铺图片】;8个参数就是切片效果
像素操作
getImageData | putImageData
获取某个canvas元素的一部分信息(getImageData ,不一定要是图片),然后可以放置在画布上的某个位置(putImageData)
ctx.rect(100,100,300,300);
ctx.fill();
var imageData = ctx.getImageData(100, 100, 50, 50);
ctx.putImageData(imageData, 500, 300);
并且可以在控制台打印获取到的元素信息,以4为一个整体,表示rgba颜色值
应用场景:
1. 拾色器
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
var img = new Image();
img.src = '1.jpg';
img.onload = function() {
ctx.drawImage(img, 0, 0);
}
function pickColor(e) {
var e = e || window.event,
x = e.layerX,
y = e.layerY,
pixel = ctx.getImageData(x, y, 1, 1),
data = pixel.data, //每个点的像素信息
rgba = 'rgba(' + data[0] + ',' + data[1] + ',' + data[2] + + ',' + data[3] / 255 + ')',//以rgba的形式展现
colorBox = document.getElementById('color');
colorBox.backgroundColor = rgba;
colorBox.innerText = rgba;
}
canvas.addEventListener('mousemove', pickColor, false);
2. 颜色灰度处理 | 颜色反相
灰度处理就取rgb的平均值,再赋值给rgb即可,取反就用255减去颜色值
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
cWidth = ctx.canvas.width,
cHeight = ctx.canvas.height,
invertBtn = document.getElementById('invertBtn'), //反相按钮
grayscaleBtn = document.getElementById('grayscaleBtn'); //灰度按钮
var img = new Image();
img.src = '1.jpg';
img.onload = function() {
draw(this);
}
function draw(img) {
ctx.drawImage(img, 0, 0);
var imgData = ctx.getImageData(x, y, cWidth, cHeight),
data = imgData.data;
// 反相操作
function invert() {
for (var i = 0; i < data.length; i++) {
data[i] = 255 - data[i];
data[i + 1] = 255 - data[i + 1];
data[i + 2] = 255 - data[i + 2];
}
ctx.putImageData(imgData, 0, 0);
}
// 灰度处理
function grayScale() {
for (var i = 0; i < data.length; i++) {
var avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg;
data[i + 1] = avg;
data[i + 2] = avg;
}
ctx.putImageData(imgData, 0, 0);
}
invertBtn.addEventListener('click', invert, false);
grayscaleBtn.addEventListener('click', grayScale, false);
}
3. 放大镜
drawImage传5个参数的时候有缩放的效果
<canvas id="canvas" width="656" height="438"></canvas>
<canvas id="zoom" width="200" height="200"></canvas>
<label for="smoothbtn">
<input type="checkbox" name="smoothbtn" checked="checked" id="smoothbtn" />
</label>
var myCan = document.getElementById('canvas'),
zoomCan = document.getElementById('zoom'),
ctx = myCan.getContext('2d'),
zoomctx = zoomCan.getContext('2d'),
smoothBtn = document.getElementById('smoothbtn');
var img = new Image();
img.src = '1.jpg';
img.onload = function() {
draw(this);
}
function draw(img) {
ctx.drawImage(img, 0, 0);
// 抗锯齿处理
function toggleSmoothing() {
zoomctx.imageSmoothingEnabled = this.checked;
zoomctx.mozimageSmoothingEnabled = this.checked;
zoomctx.webkitimageSmoothingEnabled = this.checked;
zoomctx.msimageSmoothingEnabled = this.checked;
}
smoothBtn.addEventListener('change', toggleSmoothing, false);
function zoom(e) {
var e = e || window.event,
x = e.layerX,
y = e.layerY,
zoomctx.drawImage(myCan, Math.min(Math.max(0, x - 5), img.width - 10), Math.min(Math.max(0, y - 5), img.height - 10), 10, 10, 0, 0, 200, 200);
}
myCan.addEventListener('mousemove', zoom, false);
}
动画
requestAnimationFrame 在每次需要重绘的时候执行
setInterval和setTimeout做动画最大的弊端就是不能和浏览器进行交流,不能在正确的时机进行绘制,造成跳帧的副作用。
步骤:
- 清空canvas
- 保存canvas状态(save)
- 绘制动画图形
- 恢复canvas状态(restore)
;(function() {
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
var sun = new Image();
var moon = new Image();
var earth = new Image();
sun.src = 'sun/png';
moon.src = 'moon/png';
earth.src = 'earth/png';
var init = function() {
window.requestAnimatiobFrame(drawSolarSystem);
}
function drawSolarSystem() {
ctx.globalCompositeOperation = 'destination-over';//装换层级
// 清除画布
ctx.clearRect(0, 0, 300, 300);
ctx.strokeStyle = 'rgba(0, 153, 255, 0.4)';
ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
ctx.save();
ctx.translate(earth, 0, 0); //将地球移到太阳那,然后再进行变化
// earth
var time = new Date(),
seconds = time.getSeconds(),
milliSeconds = time.getMilliseconds();
ctx.rotate((2 * Math.PI) / 60 * seconds + (2 * Math.PI) / 60000 * milliSeconds);
ctx.translate(earth, 105, 0);//从太阳那移到轨道上
ctx.fillRect(0, -12, 40, 24); //阴影
ctx.drawImage(earth, -12, -12);
ctx.restore();
// 轨道
ctx.beginPath();
ctx.arc(150, 150, 105, 0, 2 * Math.PI, false);
ctx.stroke();
// sun
ctx.drawImage(sun, 0, 0, 300, 300);
window.requestAnimatiobFrame(drawSolarSystem);
}
init();
})();