最近在研究下 html5 的 canvas 想写个小项目,练练手,结果写了一个画图板,功能点有 绘制、直线、圆、方形、涂鸦、线条粗细、颜色切换、撤销、回退、保存、下载、外部图片拖入等 , 用的技术是包含 html5 中的本地存储、下载、canvas 等技术,上图。
演示地址: http://chengxinwei.github.io/html5/2013/06/20/HTML5_CANVAS_%E7%94%BB%E5%9B%BE%E6%9D%BF/
这个项目中用到了 canvas 的很多基础功能 。在这里解释一下核心代码的思路。
在这个项目中 我用到了 2 层 canvas , 原因是当用户在画部分图形的时候希望看到绘画的过程,比如在画圆的时候,而canvas 目前支持的就是清空 和 绘制操作, 所以在这里我用了 bak 层做了一个 假象。用户一开始的所有绘制都是在 bak 层做的绘制 , 之后当鼠标松开的时候 才会到 真正的 canvas 层保存。这个是核心思路。
接下来我们来看一下代码的构造。
1.首先第一部。是做对象初始化, 包括有 初始化canvas , context , height, width 这个很简单就不做多的解释了 代码如下。
2. 第二部 就是绘制了 , 思路是当鼠标点下得时候 确定一个初始点, 当鼠标移动的时候开始绘制。之前说过 鼠标移动只是在bak 层绘制 , 当松开鼠标时把bak层的 添加到 canvas 层 。 那么这里就包含三个事件 。 点击,移动,松开 , 对应了三个不同的方法 。代码如下:
顺便提一下撤销和回退的做法。之前有提过在鼠标松开的时候,我们会把 bak 层的内容绘制到 canvas 层中, 那么在这个时候,同步的会把一份 图片信息 存到一个 数组中去,用于回滚 , 当点击撤销的时候 只需要把上一个的 图片信息取出来,在绘制一遍canvas即可。撤销回退同理
4.接下来讲一下保存功能实现。保存图片使用得 是html5 的 storage 的功能实现的。storage 是浏览器开辟了一个5M 的控件提供方开发者使用 存放key value 的键值对, 有点类似于 cookie ,那么women保存的实现就很简单了,当点击保存按钮的时候 , 获取图片的 dataUrl 保存与 storage 中即可,下次打开浏览器 获取再放入canvas中就可以了。代码如下:
5.最后说一下 下载,可能很多人因为这个头疼,因为没有后台的处理,怎么能做到下载图片呢。其实在html5中 对于 a 标签提供了一个新的属性 【download】 如:
浏览器默认会把他当做一个下载链接去处理,下载的文件名就是 download 中的 picture.png 下载的内容对应的是src 中的值。所以我们只需要把 图片的dataUrl 动态赋值上去 即可。
今天就先讲到这里哈,有问题可以给我留言。
--------------//2013-06-258 ---------------
昨天新加了 拖拽图片的功能, 从文件夹中 拖到画图板里面可以直接覆盖。
代码页很简单 如下:
简单解释一下 , 在html5支持的浏览器中, 有drop的回调函数 , 在其中获得event之后 里面有一个对象 dataTransfer.files , 获取的是 file 文件信息 , 最后通过 FileReader.readAsDataURL 的函数读入,可以获取到 html5 支持的图片信息 , 最后通过创建 image 对象,把图片绘制进去就可以了。
演示地址: http://chengxinwei.github.io/html5/2013/06/20/HTML5_CANVAS_%E7%94%BB%E5%9B%BE%E6%9D%BF/
这个项目中用到了 canvas 的很多基础功能 。在这里解释一下核心代码的思路。
在这个项目中 我用到了 2 层 canvas , 原因是当用户在画部分图形的时候希望看到绘画的过程,比如在画圆的时候,而canvas 目前支持的就是清空 和 绘制操作, 所以在这里我用了 bak 层做了一个 假象。用户一开始的所有绘制都是在 bak 层做的绘制 , 之后当鼠标松开的时候 才会到 真正的 canvas 层保存。这个是核心思路。
接下来我们来看一下代码的构造。
1.首先第一部。是做对象初始化, 包括有 初始化canvas , context , height, width 这个很简单就不做多的解释了 代码如下。
- //初始化
- var initCanvas = function(){
- canvas = document.getElementById("canvas");
- canvas.width = canvasWidth;
- canvas.height = canvasHeight;
- context = canvas.getContext('2d');
- canvasTop = $(canvas).offset().top
- canvasLeft = $(canvas).offset().left;
- canvas_bak = document.getElementById("canvas_bak");
- canvas_bak.width = canvasWidth;
- canvas_bak.height = canvasHeight;
- context_bak = canvas_bak.getContext('2d');
- }
//初始化
var initCanvas = function(){
canvas = document.getElementById("canvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
context = canvas.getContext('2d');
canvasTop = $(canvas).offset().top
canvasLeft = $(canvas).offset().left;
canvas_bak = document.getElementById("canvas_bak");
canvas_bak.width = canvasWidth;
canvas_bak.height = canvasHeight;
context_bak = canvas_bak.getContext('2d');
}
2. 第二部 就是绘制了 , 思路是当鼠标点下得时候 确定一个初始点, 当鼠标移动的时候开始绘制。之前说过 鼠标移动只是在bak 层绘制 , 当松开鼠标时把bak层的 添加到 canvas 层 。 那么这里就包含三个事件 。 点击,移动,松开 , 对应了三个不同的方法 。代码如下:
- //鼠标按下获取 开始xy开始画图
- var mousedown = function(e){
- context.strokeStyle= color;
- context_bak.strokeStyle= color;
- context_bak.lineWidth = size;
- e=e||window.event;
- startX = e.clientX - canvasLeft;
- startY = e.clientY - canvasTop;
- context_bak.moveTo(startX ,startY );
- canDraw = true;
- if(graphType == 'pencil'){
- context_bak.beginPath();
- }else if(graphType == 'circle'){
- context.beginPath();
- context.moveTo(startX ,startY );
- context.lineTo(startX +2 ,startY+2);
- context.stroke();
- }else if(graphType == 'rubber'){
- context.clearRect(startX - size * 10 , startY - size * 10 , size * 20 , size * 20);
- }
- };
- //鼠标离开 把蒙版canvas的图片生成到canvas中
- var mouseup = function(e){
- e=e||window.event;
- canDraw = false;
- var image = new Image();
- if(graphType!='rubber'){
- image.src = canvas_bak.toDataURL();
- image.onload = function(){
- context.drawImage(image , 0 ,0 , image.width , image.height , 0 ,0 , canvasWidth , canvasHeight);
- clearContext();
- saveImageToAry();
- }
- var x = e.clientX - canvasLeft;
- var y = e.clientY - canvasTop;
- context.beginPath();
- context.moveTo(x ,y );
- context.lineTo(x +2 ,y+2);
- context.stroke();
- }
- };
- // 鼠标移动
- var mousemove = function(e){
- e=e||window.event;
- var x = e.clientX - canvasLeft;
- var y = e.clientY - canvasTop;
- //方块 4条直线搞定
- if(graphType == 'square'){
- if(canDraw){
- context_bak.beginPath();
- clearContext();
- context_bak.moveTo(startX , startY);
- context_bak.lineTo(x ,startY );
- context_bak.lineTo(x ,y );
- context_bak.lineTo(startX ,y );
- context_bak.lineTo(startX ,startY );
- context_bak.stroke();
- }
- //直线
- }else if(graphType =='line'){
- if(canDraw){
- context_bak.beginPath();
- clearContext();
- context_bak.moveTo(startX , startY);
- context_bak.lineTo(x ,y );
- context_bak.stroke();
- }
- //画笔
- }else if(graphType == 'pencil'){
- if(canDraw){
- context_bak.lineTo(e.clientX - canvasLeft ,e.clientY - canvasTop);
- context_bak.stroke();
- }
- //圆 未画得时候 出现一个小圆
- }else if(graphType == 'circle'){
- clearContext();
- if(canDraw){
- context_bak.beginPath();
- var radii = Math.sqrt((startX - x) * (startX - x) + (startY - y) * (startY - y));
- context_bak.arc(startX,startY,radii,0,Math.PI * 2,false);
- context_bak.stroke();
- }else{
- context_bak.beginPath();
- context_bak.arc(x,y,20,0,Math.PI * 2,false);
- context_bak.stroke();
- }
- //涂鸦 未画得时候 出现一个小圆
- }else if(graphType == 'handwriting'){
- if(canDraw){
- context_bak.beginPath();
- context_bak.strokeStyle = color;
- context_bak.fillStyle = color;
- context_bak.arc(x,y,size*10,0,Math.PI * 2,false);
- context_bak.fill();
- context_bak.stroke();
- context_bak.restore();
- }else{
- clearContext();
- context_bak.beginPath();
- context_bak.fillStyle = color;
- context_bak.arc(x,y,size*10,0,Math.PI * 2,false);
- context_bak.fill();
- context_bak.stroke();
- }
- //橡皮擦 不管有没有在画都出现小方块 按下鼠标 开始清空区域
- }else if(graphType == 'rubber'){
- context_bak.lineWidth = 1;
- clearContext();
- context_bak.beginPath();
- context_bak.strokeStyle = '#000000';
- context_bak.moveTo(x - size * 10 , y - size * 10 );
- context_bak.lineTo(x + size * 10 , y - size * 10 );
- context_bak.lineTo(x + size * 10 , y + size * 10 );
- context_bak.lineTo(x - size * 10 , y + size * 10 );
- context_bak.lineTo(x - size * 10 , y - size * 10 );
- context_bak.stroke();
- if(canDraw){
- context.clearRect(x - size * 10 , y - size * 10 , size * 20 , size * 20);
- }
- }
- };
//鼠标按下获取 开始xy开始画图
var mousedown = function(e){
context.strokeStyle= color;
context_bak.strokeStyle= color;
context_bak.lineWidth = size;
e=e||window.event;
startX = e.clientX - canvasLeft;
startY = e.clientY - canvasTop;
context_bak.moveTo(startX ,startY );
canDraw = true;
if(graphType == 'pencil'){
context_bak.beginPath();
}else if(graphType == 'circle'){
context.beginPath();
context.moveTo(startX ,startY );
context.lineTo(startX +2 ,startY+2);
context.stroke();
}else if(graphType == 'rubber'){
context.clearRect(startX - size * 10 , startY - size * 10 , size * 20 , size * 20);
}
};
//鼠标离开 把蒙版canvas的图片生成到canvas中
var mouseup = function(e){
e=e||window.event;
canDraw = false;
var image = new Image();
if(graphType!='rubber'){
image.src = canvas_bak.toDataURL();
image.onload = function(){
context.drawImage(image , 0 ,0 , image.width , image.height , 0 ,0 , canvasWidth , canvasHeight);
clearContext();
saveImageToAry();
}
var x = e.clientX - canvasLeft;
var y = e.clientY - canvasTop;
context.beginPath();
context.moveTo(x ,y );
context.lineTo(x +2 ,y+2);
context.stroke();
}
};
// 鼠标移动
var mousemove = function(e){
e=e||window.event;
var x = e.clientX - canvasLeft;
var y = e.clientY - canvasTop;
//方块 4条直线搞定
if(graphType == 'square'){
if(canDraw){
context_bak.beginPath();
clearContext();
context_bak.moveTo(startX , startY);
context_bak.lineTo(x ,startY );
context_bak.lineTo(x ,y );
context_bak.lineTo(startX ,y );
context_bak.lineTo(startX ,startY );
context_bak.stroke();
}
//直线
}else if(graphType =='line'){
if(canDraw){
context_bak.beginPath();
clearContext();
context_bak.moveTo(startX , startY);
context_bak.lineTo(x ,y );
context_bak.stroke();
}
//画笔
}else if(graphType == 'pencil'){
if(canDraw){
context_bak.lineTo(e.clientX - canvasLeft ,e.clientY - canvasTop);
context_bak.stroke();
}
//圆 未画得时候 出现一个小圆
}else if(graphType == 'circle'){
clearContext();
if(canDraw){
context_bak.beginPath();
var radii = Math.sqrt((startX - x) * (startX - x) + (startY - y) * (startY - y));
context_bak.arc(startX,startY,radii,0,Math.PI * 2,false);
context_bak.stroke();
}else{
context_bak.beginPath();
context_bak.arc(x,y,20,0,Math.PI * 2,false);
context_bak.stroke();
}
//涂鸦 未画得时候 出现一个小圆
}else if(graphType == 'handwriting'){
if(canDraw){
context_bak.beginPath();
context_bak.strokeStyle = color;
context_bak.fillStyle = color;
context_bak.arc(x,y,size*10,0,Math.PI * 2,false);
context_bak.fill();
context_bak.stroke();
context_bak.restore();
}else{
clearContext();
context_bak.beginPath();
context_bak.fillStyle = color;
context_bak.arc(x,y,size*10,0,Math.PI * 2,false);
context_bak.fill();
context_bak.stroke();
}
//橡皮擦 不管有没有在画都出现小方块 按下鼠标 开始清空区域
}else if(graphType == 'rubber'){
context_bak.lineWidth = 1;
clearContext();
context_bak.beginPath();
context_bak.strokeStyle = '#000000';
context_bak.moveTo(x - size * 10 , y - size * 10 );
context_bak.lineTo(x + size * 10 , y - size * 10 );
context_bak.lineTo(x + size * 10 , y + size * 10 );
context_bak.lineTo(x - size * 10 , y + size * 10 );
context_bak.lineTo(x - size * 10 , y - size * 10 );
context_bak.stroke();
if(canDraw){
context.clearRect(x - size * 10 , y - size * 10 , size * 20 , size * 20);
}
}
};
顺便提一下撤销和回退的做法。之前有提过在鼠标松开的时候,我们会把 bak 层的内容绘制到 canvas 层中, 那么在这个时候,同步的会把一份 图片信息 存到一个 数组中去,用于回滚 , 当点击撤销的时候 只需要把上一个的 图片信息取出来,在绘制一遍canvas即可。撤销回退同理
4.接下来讲一下保存功能实现。保存图片使用得 是html5 的 storage 的功能实现的。storage 是浏览器开辟了一个5M 的控件提供方开发者使用 存放key value 的键值对, 有点类似于 cookie ,那么women保存的实现就很简单了,当点击保存按钮的时候 , 获取图片的 dataUrl 保存与 storage 中即可,下次打开浏览器 获取再放入canvas中就可以了。代码如下:
- //保存
- var save = function(){
- for(var i = 1;i<=8;i++){
- var dataUrl = getStorage(i);
- if(dataUrl == null || dataUrl == ''){
- putStorage(i,canvas.toDataURL());
- $("#history_"+i).attr("src",canvas.toDataURL());
- initHistorty();
- return ;
- }
- }
- }
//保存
var save = function(){
for(var i = 1;i<=8;i++){
var dataUrl = getStorage(i);
if(dataUrl == null || dataUrl == ''){
putStorage(i,canvas.toDataURL());
$("#history_"+i).attr("src",canvas.toDataURL());
initHistorty();
return ;
}
}
}
5.最后说一下 下载,可能很多人因为这个头疼,因为没有后台的处理,怎么能做到下载图片呢。其实在html5中 对于 a 标签提供了一个新的属性 【download】 如:
- <a href="javascript:void(0);" id="history_download_1" download="picture.png">下载</a></td>
<a href="javascript:void(0);" id="history_download_1" download="picture.png">下载</a></td>
浏览器默认会把他当做一个下载链接去处理,下载的文件名就是 download 中的 picture.png 下载的内容对应的是src 中的值。所以我们只需要把 图片的dataUrl 动态赋值上去 即可。
今天就先讲到这里哈,有问题可以给我留言。
--------------//2013-06-258 ---------------
昨天新加了 拖拽图片的功能, 从文件夹中 拖到画图板里面可以直接覆盖。
代码页很简单 如下:
- // 处理文件拖入事件,防止浏览器默认事件带来的重定向
- function handleDragOver(evt) {
- evt.stopPropagation();
- evt.preventDefault();
- }
- // 判断是否图片
- function isImage(type) {
- switch (type) {
- case 'image/jpeg':
- case 'image/png':
- case 'image/gif':
- case 'image/bmp':
- case 'image/jpg':
- return true;
- default:
- return false;
- }
- }
- // 处理拖放文件列表
- function handleFileSelect(evt) {
- evt.stopPropagation();
- evt.preventDefault();
- var files = evt.dataTransfer.files;
- for (var i = 0, f; f = files[i]; i++) {
- var t = f.type ? f.type : 'n/a';
- reader = new FileReader();
- isImg = isImage(t);
- // 处理得到的图片
- if (isImg) {
- reader.onload = (function (theFile) {
- return function (e) {
- var image = new Image();
- image.src = e.target.result ;
- image.onload = function(){
- context.drawImage(image , 0 ,0 , image.width , image.height , 0 ,0 , canvasWidth , canvasHeight);
- }
- };
- })(f)
- reader.readAsDataURL(f);
- }
- }
- }
- //初始化拖入效果
- var initDrag= function(){
- var dragDiv = document.getElementById("canvas_bak");
- dragDiv.addEventListener('dragover', handleDragOver, false);
- dragDiv.addEventListener('drop', handleFileSelect, false);
- }
// 处理文件拖入事件,防止浏览器默认事件带来的重定向
function handleDragOver(evt) {
evt.stopPropagation();
evt.preventDefault();
}
// 判断是否图片
function isImage(type) {
switch (type) {
case 'image/jpeg':
case 'image/png':
case 'image/gif':
case 'image/bmp':
case 'image/jpg':
return true;
default:
return false;
}
}
// 处理拖放文件列表
function handleFileSelect(evt) {
evt.stopPropagation();
evt.preventDefault();
var files = evt.dataTransfer.files;
for (var i = 0, f; f = files[i]; i++) {
var t = f.type ? f.type : 'n/a';
reader = new FileReader();
isImg = isImage(t);
// 处理得到的图片
if (isImg) {
reader.onload = (function (theFile) {
return function (e) {
var image = new Image();
image.src = e.target.result ;
image.onload = function(){
context.drawImage(image , 0 ,0 , image.width , image.height , 0 ,0 , canvasWidth , canvasHeight);
}
};
})(f)
reader.readAsDataURL(f);
}
}
}
//初始化拖入效果
var initDrag= function(){
var dragDiv = document.getElementById("canvas_bak");
dragDiv.addEventListener('dragover', handleDragOver, false);
dragDiv.addEventListener('drop', handleFileSelect, false);
}
简单解释一下 , 在html5支持的浏览器中, 有drop的回调函数 , 在其中获得event之后 里面有一个对象 dataTransfer.files , 获取的是 file 文件信息 , 最后通过 FileReader.readAsDataURL 的函数读入,可以获取到 html5 支持的图片信息 , 最后通过创建 image 对象,把图片绘制进去就可以了。