javascript
javascript全栈开发实践-web-4
接下來,我們繼續(xù)完善功能。作為一個手寫應用,undo/redo操作是必須的。要現(xiàn)實undo/redo,最容易想到的實現(xiàn)方式,就是我們記住每一次操作的結果,在用戶undo的時候,顯示之前的結果給用戶就可以了。但是這樣有一個很大的問題,就是我們的canvas,實際上一張圖片。要記住結果,就需要記住這張圖片的內容。而圖片本身的數(shù)據(jù)量是很大的。因此我們沒有辦法記住很多次結果,也就是限制了undo的次數(shù)。 還有一種辦法,就是我們記住用戶的操作,例如我們記住第一次用戶用鉛筆,畫了一條線,這條線上面每一個點的坐標是什么,我們都給記下來。在用戶undo的時候,我們可以清空畫板,然后從頭到尾再畫一次所有的操作,這樣就可以實現(xiàn)undo功能了。由于用戶操作相對的數(shù)據(jù),相對于圖片本身的數(shù)據(jù)來說,要小很多,因此,我們幾乎可以無限制的undo/redo了。 要記住用戶操作,那么在鼠標操作的時候,我們就需要記住每一個鼠標的位置,然后加入到數(shù)組里面。在用戶鼠標松開的時候,我們再把整個路徑的坐標,以及筆畫粗細,顏色,作為一個完整的操作,添加到用戶數(shù)組里面。 下面是完整的代碼
<html><head><meta charset="utf-8"></head><body style='background:lightgrey'><div><button id='pencil' onclick="handleChoosePencil()">pencil</button><button id='highlighter' onclick="handleChooseHighlighter()">highlighter</button><button id='eraser' onclick="handleChooseEraser()">eraser</button><button id='undo' onclick="handleUndo()">undo</button><button id='redo' onclick="handleRedo()">redo</button></div><canvas id='pad' width='800px' height='600px' style='background:white'></canvas></body><script>//let actions = [];let points = [];let undoCursor = -1;//const pad = document.getElementById('pad');const ctx = pad.getContext('2d');ctx.lineWidth = 2;ctx.strokeStyle = 'blue';//updateButtonStatus();////pad.addEventListener('mousedown', handleMouseDown);//function handleMouseDown(event) {//if (undoCursor != -1) {actions = actions.slice(0, undoCursor);}undoCursor = -1;//ctx.beginPath();ctx.moveTo(event.offsetX, event.offsetY);//pad.addEventListener('mousemove', handleMouseMove);pad.addEventListener('mouseup', handleMouseUp);//points.push({x: event.offsetX, y: event.offsetY}); }//function handleMouseMove(event) {ctx.lineTo(event.offsetX, event.offsetY);ctx.stroke();ctx.beginPath();ctx.moveTo(event.offsetX, event.offsetY);points.push({x: event.offsetX, y: event.offsetY}); }//function handleMouseUp(event) {pad.removeEventListener('mousemove', handleMouseMove);pad.removeEventListener('mouseup', handleMouseUp);//actions.push({lineWidth: ctx.lineWidth,strokeStyle: ctx.strokeStyle,points,});//points = [];updateButtonStatus();}//function handleChoosePencil(event) {ctx.strokeStyle = 'rgb(0, 0, 255)';ctx.lineWidth = 2;}//function handleChooseHighlighter(event) {ctx.strokeStyle = 'rgba(255, 255, 0, 0.5)';ctx.lineWidth = 8;}//function handleChooseEraser(event) {ctx.strokeStyle = 'white';ctx.lineWidth = 8;}//function canUndo() {if (actions.length == 0) {return false;}//if (undoCursor == 0) {return false;}//return true;}//function canRedo() {//if (actions.length == 0) {return false;}//if (undoCursor == -1 || undoCursor == actions.length) {return false;}//return true;}//function handleUndo(event) {if (!canUndo()) {return;}//if (undoCursor == -1) {undoCursor = actions.length;}//undoCursor--;//repaint();//updateButtonStatus();}//function handleRedo(event) {if (!canRedo()) {return;}//undoCursor++;//repaint();//updateButtonStatus();}//function updateButtonStatus() {const undoButton = document.getElementById('undo');const redoButton = document.getElementById('redo');undoButton.disabled = !canUndo();redoButton.disabled = !canRedo();}//function repaint() {ctx.clearRect(0, 0, pad.width, pad.height);//let toIndex = undoCursor == -1 ? actions.length : undoCursor;for (let i = 0; i < toIndex; i++) {//let action = actions[i];//ctx.beginPath();ctx.lineWidth = action.lineWidth;ctx.strokeStyle = action.strokeStyle;//let points = action.points;if (points.length == 0) {continue;}//let firstPoint = points[0];ctx.moveTo(firstPoint.x, firstPoint.y);for (let j = 1; j < points.length; j++) {const point = points[j];ctx.lineTo(point.x, point.y);}ctx.stroke();//}}</script> </html> 復制代碼記錄用戶操作:
首先,我們定義了一個points數(shù)組,用來在鼠標按下以及移動的時候,記錄完整的鼠標坐標。 我們還定義了一個actions數(shù)組,這個就是用來存放用戶每一個操作的數(shù)組。在用戶鼠標松開的時候,我們會把當前操作添加到這個actions數(shù)組后面:
function handleMouseUp(event) {pad.removeEventListener('mousemove', handleMouseMove);pad.removeEventListener('mouseup', handleMouseUp);//actions.push({lineWidth: ctx.lineWidth,strokeStyle: ctx.strokeStyle,points,});//points = [];updateButtonStatus();} 復制代碼我們在這里記錄了當前路徑所有的坐標(points),當前路徑的寬度,以及顏色。把他們組合成一個對象添加到actions數(shù)組里面。在添加完之后,我們還需要記住,要把points數(shù)組重置,準備記錄下一個操作的坐標。
執(zhí)行undo
在用戶進行undo的時候,最容易想到的辦法,就是用戶每執(zhí)行一次undo,我們就把actions數(shù)組中最后一個元素刪除,然后重新繪制actions里面的所有元素。但是這樣以來,我們就沒辦法實現(xiàn)redo操作了。因此,在用戶undo的時候,我們可以通過一個游標(actions數(shù)組下標),記錄當前用戶undo到哪一步了。當用戶undo的時候,游標向數(shù)組頭部移動。當用戶redo的時候,游標向數(shù)組尾部移動。 首先,我們需要定義一個undoCursor,并把他的初始值設置成-1。之所以設置成-1,是因為數(shù)組的下標永遠應該是大于等于0的。那么如果是-1,表示當前游標在數(shù)組最后面,用戶沒有任何undo操作。 下面的代碼,可以判斷當前是否允許undo/redo
function canUndo() {if (actions.length == 0) {return false;}//if (undoCursor == 0) {return false;}//return true;}function canRedo() {//if (actions.length == 0) {return false;}//if (undoCursor == -1 || undoCursor == actions.length) {return false;}//return true;} 復制代碼當用戶undo/redo的時候,我們去移動游標:
function handleUndo(event) {if (!canUndo()) {return;}//if (undoCursor == -1) {undoCursor = actions.length;}//undoCursor--;//repaint();//updateButtonStatus();}//function handleRedo(event) {if (!canRedo()) {return;}//undoCursor++;//repaint();//updateButtonStatus();} 復制代碼在移動完游標后,我們還需要進行重繪。 重繪很簡單,就是清空canvas,然后從頭到尾重新繪制路徑即可:
function repaint() {ctx.clearRect(0, 0, pad.width, pad.height);//let toIndex = undoCursor == -1 ? actions.length : undoCursor;for (let i = 0; i < toIndex; i++) {//let action = actions[i];//ctx.beginPath();ctx.lineWidth = action.lineWidth;ctx.strokeStyle = action.strokeStyle;//let points = action.points;if (points.length == 0) {continue;}//let firstPoint = points[0];ctx.moveTo(firstPoint.x, firstPoint.y);for (let j = 1; j < points.length; j++) {const point = points[j];ctx.lineTo(point.x, point.y);}ctx.stroke();//}} 復制代碼當用戶undo/redo之后,繼續(xù)進行新的繪畫的時候,我們還需要刪除游標后面的數(shù)據(jù),并且重置游標的位置:
if (undoCursor != -1) {actions = actions.slice(0, undoCursor);}undoCursor = -1; 復制代碼最后,我們還需要在添加新的undo/redo按鈕,并響應按鈕消息,進行undo/redo處理。同時,在合適的時機,我們還需要更新undo/redo按鈕的狀態(tài),以便告訴用戶,什么時候可以undo/redo。
轉載于:https://juejin.im/post/5cc2e60f6fb9a0321b6970c4
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結
以上是生活随笔為你收集整理的javascript全栈开发实践-web-4的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 太危险!女子驾驶电车高速途中突然失速 厂
- 下一篇: “狗”气越来越足!第二代哈弗大狗正式上市