canvas 和 history 结合使用完成的一个示例;示例中显示一个canvas 元素,用户可以在该canvas 元素中随意使用鼠标书写文字或绘制图画,当用户单击一次或连续单击浏览器后退按钮时,可以撤销当前绘制的一笔或多笔,当用户单击或连续点击浏览器前进按钮时,可以重绘当前书写或绘制的最后一笔或多笔。
html代码清单如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>结合使用 history 和 canvas </title>
<style>
#canvas {position: absolute;top:0;left:0;width:100%;height:100%;margin:0;display:block;}
</style>
</head>
<body>
<canvas id="canvas" width="500px" height="300px"></canvas>
<img id="img" src="red.jpg" alt="图片" />
</body>
</html>
页面中的image 元素用于在页面中加载一个小圆点图片,当在canvas 元素中按下并连续拖动鼠标的时候,按照用户的鼠标拖动轨迹连续绘制该小圆点,这样处理之后会在浏览器中显示用户书写文字或绘制图画是所绘制的每一笔。
接下来,对该示例页面添加JavaScript 代码,首先定义引用 image 元素的image 全局变量、引用 canvas 元素的canvas 全局变量、引用canvas 元素的上下文对象的 context 全局变量,以及用于控制是否继续进行绘制操作的布尔型全局变量 isDrawing ,当isDrawing 的值为 true 时代表用户已按下鼠标左键,可以继续绘制,当该值为 false 时,表示用户已经松开鼠标左键,绘制停止。
var image = document.getElementById("img"); var canvas = document.getElementById("canvas"); var context = canvas.getContext("2d"); var isDrawing = false;
接下来先屏蔽用户在canvas 元素中通过按下鼠标左键、以手指或手写笔触发的 pointerdown 事件(pointerdown 事件属于一种 touch event 事件)
canvas.addEventListener("pointerdown",function(event){event.preventManipulation()},false);
然后,监听用户在canvas 元素中按下鼠标左键时触发的 mousedown 事件,并将事件处理函数指定为 startDrawing 函数;监听用户在 canvas 元素中移动鼠标时触发的 mousemove 事件,并将事件处理函数指定为 draw 函数;监听用户在canvas 元素中松开鼠标左键时触发的 mouseup 事件,并将事件处理函数指定为 stopDrawing 函数;监听用户单机浏览器后退按钮或前进按钮触发的 popstate 事件,并将事件处理函数指定为 loadState 函数;
canvas.addEventListener("mousedown",startDrawing,false); canvas.addEventListener("mousemove",draw,false); canvas.addEventListener("mouseup",stopDrawing,false); window.addEventListener("popstate",loadState,false);
在startDrawing 函数中,我们定义用户在canvas 元素中按下鼠标左键时将全局变量 isDrawing 的值设为 true ,表示用户开始书写文字或绘制图画;
function startDrawing(){ isDrawing = true; }
在 draw 函数中,定义当用户在canvas 元素中移动鼠标左键时首先判断全局布尔变量是否是 true ,如果是 true,则表示用户已经按下鼠标左键,则在鼠标左键所在位置使用image 元素绘制小圆点
function draw(event){ if(isDrawing){ var sx = canvas.width/canvas.offsetWidth; var sy = canvas.height/canvas.offsetHeight; var x = sx*event.clientX - image.naturalWidth/2; var y = sy*event.clientY - image.naturalHeight/2; context.drawImage(image,x,y); } }
在stopDrawing 函数中,首先定义当用户在canvas 元素中松开鼠标左键时将全局布尔变量的值设为 false,表示用户已经停止书写或绘制图画,之后当用户在canvas 元素中移动鼠标而不是按下鼠标左键时不执行绘制操作。然后,使用history api 中的pushState 方法将当前所绘制的图像保存在浏览器的历史记录中。
function stopDrawing(){ isDrawing = false; var state = context.getImageData(0,0,canvas.width,canvas.height); history.pushState(state,null); }
在loadState 函数中定义,当用户单击浏览器的后退按钮或前进按钮时,首先清除 canvas 元素中的图像,然后读取触发 popstate 事件的事件对象的 state 属性值,该属性值即为执行 pushState 方法时所使用的第一个参数值,其中保存了在向浏览器历史记录中添加记录时同步保存的对象,在本例中为一个保存了由 canvas 元素中的所有像素构成的数组的CanvasPixelArray对象。接下来调用 canvas 元素的上下文对象的 putImageData 方法在 canvas 元素中输出保存在CanvasPixelArray 对象中的所有像素,即将每一个历史记录中保存的图像绘制在 canvas 元素中
function loadState(e){ context.clearRect(0,0,canvas.width,canvas.height); if(e.state){ context.putImageData(e.state,0,0); } }
最后,该页面还存在一个问题,即当用户在canvas 元素中绘制了多笔之后,重新再浏览器的地址栏中输入页面地址(这时浏览器中的 canvas 元素显示空白图像),然后绘制第一笔,之后再单击浏览器的后退按钮时,canvas 元素中并不显示空白图像,而是直接显示输入页面地址之前的绘制图像,这样看起来浏览器中的历史记录并不连贯,因为 canvas 元素中缺少了一幅空白的图像。为了修正这个问题,我们在页面打开时就将 canvas 元素中的空白图像保存在历史记录中
var isDrawing = false; var state = context.getImageData(0,0,canvas.width,canvas.height); history.pushState(state,null);
在浏览器打开示例页面,然后使用鼠标左键随便绘制几笔,效果如下
单击浏览器后退按钮,canvas 元素中显示绘制最后一笔之前的图像