论坛首页 Web前端技术论坛

HTML5+JS 画图板

浏览 32156 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2013-06-05   最后修改:2013-06-25
      最近在研究下 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  这个很简单就不做多的解释了 代码如下。
//初始化
		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);
										
			}			
		}
	};




顺便提一下撤销和回退的做法。之前有提过在鼠标松开的时候,我们会把 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 ;
		}
	}			
}



5.最后说一下 下载,可能很多人因为这个头疼,因为没有后台的处理,怎么能做到下载图片呢。其实在html5中 对于 a 标签提供了一个新的属性 【download】 如:
<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);  
		}



简单解释一下 , 在html5支持的浏览器中, 有drop的回调函数 , 在其中获得event之后 里面有一个对象 dataTransfer.files , 获取的是 file 文件信息 , 最后通过 FileReader.readAsDataURL  的函数读入,可以获取到 html5 支持的图片信息 , 最后通过创建 image 对象,把图片绘制进去就可以了。




有兴趣探讨的 或者想要源码的读者可以给我留言 ,我会定时回复 谢谢!


  • 大小: 82.2 KB
  • 大小: 103.7 KB
  • 大小: 108.5 KB
   发表时间:2013-06-05  
写的很好啊,我练手的时候怎么就没写出来呢,当时只写了个验证码……
0 请登录后投票
   发表时间:2013-06-06  
楼主最好能把你的代码详细的解释一下吧
0 请登录后投票
   发表时间:2013-06-06  
LZ  好样的。。。
学习了!
0 请登录后投票
   发表时间:2013-06-15  
最近正好在用这方面的知识,学习了。感谢分享
0 请登录后投票
   发表时间:2013-06-25  
airhunter 写道
楼主最好能把你的代码详细的解释一下吧

我抽空尽量把代码解释了一下,可以再看一下 ,有不懂的可以给我留言
0 请登录后投票
   发表时间:2013-08-01  
很好很强大...
0 请登录后投票
   发表时间:2013-10-03  
楼主的源代码,是只有draw.js和stroage.js两个文件是吧?

能不能增加一个windows画板中text的功能呢?
0 请登录后投票
   发表时间:2013-10-09  
有时间细读
0 请登录后投票
   发表时间:2013-10-31  
赞一个,支持一下
0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics