首页 > 编程笔记 > JavaScript笔记 > JS BOM

JS实现可回退的画板

本例利用 History API 的状态对象实时记录用户的每一操作,把每一次操作信息传递给浏览器的历史记录保存起来,这样当用户单击浏览器的“后退”按钮时,会逐步恢复前面的操作状态,从而实现历史恢复功能。在示例页面中显示一个 canvas 元素,用户可以在该 canvas 元素中随意使用鼠标绘画,当用户单击一次或连续单击浏览器的“后退”按钮时,可以撤销当前绘制的最后一笔或多笔,当用户单击一次或多次连续单击浏览器的“前进”按钮时,可以重绘当前书写或绘制的最后一笔或多笔,演示效果如下图所示:


 

操作步骤

1) 设计文档结构。本例利用 canvas 元素把页面设计为一块画板,image 元素用于在页面中加载一个黑色小圆点,当用户在 canvas 元素中按下并连续拖动鼠标左键时,根据鼠标拖动轨迹连续绘制该黑色小圆点,这样处理之后会在浏览器中显示用户绘画时所产生的的每一笔。
<canvas id="canvas"></canvas>
<img id="image" src="brush.png" style="display:none;" />

2) 设计 CSS 样式,定义 canvas 元素满屏显示。
#canvas {
    position : absolute; top : 0; left : 0; width : 100%; height : 100%;
    margin : 0; display : block;
}

3) 添加 JavaScript 脚本。首先,定义引用 image 元素的 image 全局变量、引用 canvas 元素的全局变量、引用 canvas 元素的上下文对象的 context 全局变量,以及用于控制是否继续进行绘制操作的布尔型全局变量 isDrawing,当 isDrawing 的值为 true 时表示用户已经按下鼠标左键,可以继续绘制,当值为 false 时表示用户已经松开鼠标左键,停止绘制。
var image = document.getElementById ("image");
var canvas = document.getElementById ("canvas");
var context = canvas.getContext ("2d");
var isDrawing = false;

4) 屏蔽用户在 canvas 元素中通过按下鼠标左键、以手指或手写笔触发的 pointerdown 事件,它属于一种 touch 事件。
canvas.addEventListener ("pointerdown", function (e) {
    e.preventManipulation (
)}, false);

5) 监听用户在 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", function (e) {
    loadState (e.state);
});

6) 在 startDrawing() 函数中,定义当用户在 canvas 元素中按下鼠标左键时,将全局布尔型变量 isDrawing 的变量值设为 true,表示用户开始书写文字或绘制图画。
function startDrawing () {
    isDrawing = true;
}

7) 在 draw() 函数中,定义当用户在 canvas 元素中按下鼠标左键时,先判断全局布尔型变量 isDrawing 的变量值是否为 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);
    }
}

8) 在 stopDrawing() 函数中,先定义当用户在 canvas 元素中松开鼠标左键时,将全局布尔型变量 isDrawing 的变量值设为 false,表示用户已经停止书写文字或绘制图画。当用户在 canvas 元素中不按下鼠标左键而直接移动鼠标时,不执行绘制操作。
function stopDrawing () {
    isDrawing = false;
}

9) 使用 History API 的 pushState() 方法将当前所绘图像保存在浏览器的历史记录中。
function stopDrawing () {
    isDrawing = false;
    var state = context.getImageData (0, 0, canvas.width, canvas.height);
    history.pushState (state, null);
}
在本例中,将 pushState() 方法的第 1 个参数值设置为一个 CanvasPixelArray 对象,在该对象中保存了由 canvas 元素中的所有像素所构成的数组。

10) 在 loadState() 函数中定义当用户单击浏览器的“后退”按钮或“前进”按钮时,首先清除 canvas 元素中的图像,然后读取触发 popstate 事件的事件对象的 state 属性值,该属性值即为执行 pushState() 方法时所使用的第一个参数值,其中保存了在向浏览器历史记录中添加记录时同步保存的对象,在本例中为一个保存了由 canvas 元素中的所有像素构成的数组的 CanvasPixelArray 对象。

最后,调用 canvas 元素的上下文对象的 putImageData() 方法,在 canvas 元素中输出保存在 CanvasPixelArray 对象中的所有像素,即将每一个历史记录中所保存的图像绘制在 canvas 元素中。
function loadState (state) {
    context.clearRect (0, 0, canvas.width, canvas.height);
    if (state) {
        context.putImageData (state, 0, 0);
    }
}

11) 当用户在 canvas 元素中绘制多笔之后,重新在浏览器的地址栏中输入页面地址,然后重新绘制第一笔,单击浏览器“后退”按钮,canvas 元素中并不显示空白图像,而是直接显示输入页面地址之前的绘制图像,这样看起来浏览器中的历史记录并不连贯,因为 canvas 元素中缺少了一幅空白图像。为此,设计在页面打开时就将 canvas 元素中的空白图像保存在历史记录中。
var state = context.getImageData (0, 0, canvas.width, canvas.height);
history.pushState (state, null);

所有教程

优秀文章