Canvas

148 阅读5分钟

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做动画最大的弊端就是不能和浏览器进行交流,不能在正确的时机进行绘制,造成跳帧的副作用。

步骤:

  1. 清空canvas
  2. 保存canvas状态(save)
  3. 绘制动画图形
  4. 恢复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();
})();