iT邦幫忙

2018 iT 邦幫忙鐵人賽
7
Modern Web

重新認識 JavaScript系列 第 32

重新認識 JavaScript 番外篇 (2) - 你所不知道的 pushState

本系列文章已重新編修,並在加入部分 ES6 新篇章後集結成書,有興趣的朋友可至天瓏書局選購,感謝大家支持。

購書連結 https://www.tenlong.com.tw/products/9789864344130

讓我們再次重新認識 JavaScript!


重新認識 JavaScript: Day 28 從 Page 到 Application,談談前端框架與工具庫 (中) 文中我們介紹 SPA 與前端的 URL 路由控制時,曾經提過 HTML5 的 History API 可以做到在網頁的歷史紀錄中往上一步或往下一步移動。

你以為 History API 就只能做到這樣嗎?

實際上,我們還可以利用 pushState 來儲存物件的狀態,並且透過 window.history.go() 來取得「往前」或「往後」一步的狀態。

先來個 Canvas 畫板吧

HTML 的部分很簡單,就一個 <canvas> 畫布元素再加上一些按鈕控制項

<canvas id="canvas" width="300" height="300"></canvas>

<div id="colors">
  <span style="background-color: black;" class="black"></span>
  <span style="background-color: red;" class="red"></span>
  <span style="background-color: blue;" class="blue"></span>
  <span style="background-color: green;" class="green"></span>
</div>

<button class="clear">清除</button>

JavaScript 的部分,則是針對 canvas 畫布與滑鼠事件作控制,可以看到整個畫布的程式碼沒幾行

const canvas = document.getElementById('canvas'),
      colors = document.querySelectorAll('#colors > span'),
      clear = document.querySelector('.clear');

// 畫筆粗細、顏色的預設狀態
const context = canvas.getContext('2d');
context.lineWidth = 4;
context.strokeStyle = '#000';

// 畫筆狀態
let isDrawing = false;

// 切換顏色
for( let i = 0; i < colors.length; i++ ){
  colors[i].addEventListener('click', function(){
    context.strokeStyle = this.className;
  }, false);
}

// reset
clear.addEventListener('click', function(){
  context.clearRect(0, 0, canvas.width, canvas.height);
}, false);

// canvas 內滑鼠操作事件
canvas.addEventListener('mousedown', startDarw, false);
canvas.addEventListener('mousemove', drawing,   false);
canvas.addEventListener('mouseup',   stopDrawing, false);

function startDarw(e){
  isDrawing = true;
  context.beginPath();
  context.moveTo(e.pageX - canvas.offsetLeft, e.pageY - canvas.offsetTop);
}

function drawing(e){
  if(isDrawing){
    context.lineTo(e.pageX - canvas.offsetLeft, e.pageY - canvas.offsetTop);
    context.stroke();
  }
}

function stopDrawing(e){
  isDrawing = false;
}

做出來像這樣
https://i.imgur.com/i7A3TIX.gif

附上 JSBin 網址給大家玩: http://jsbin.com/jisehafege/edit?html,js,output


但怎麼利用 canvas 來做畫布並不是今天想說的主題,相關教學可以去 Google 找。

像大家應該都用過的「windows 小畫家」除了也有畫筆功能之外,還有一個很重要的功能,就是「復原」(undo) 以及「取消復原」(redo) 的功能。

如果我們想要在剛剛做好的畫布來實做這兩個功能的話,肯定要不斷地去記錄使用者對畫布的每一個操作對吧? 像這種時候,我們就剛好可以利用 HTML5 的 History API 來實作。

首先,在開始的時候我們先取得畫布的初始狀態,然後透過 window.history.pushState() 存起來。

let state = context.getImageData(0, 0, canvas.width, canvas.height);
window.history.pushState(state, null);

再來,在每一次畫筆結束時的 stopDrawing(),我們再對 window.history 做一樣的事:

function stopDrawing(e){
  isDrawing = false;

  var state = context.getImageData(0, 0, canvas.width, canvas.height);
  window.history.pushState(state, null);
}

然後新增 popstate 事件與對應的 Handler:

window.addEventListener('popstate', changeStep, false);

function changeStep(e){
  // 清除畫布
  context.clearRect(0, 0, canvas.width, canvas.height);

  // 透過 e.state 取得先前存到 window.history 的狀態
  if( e.state ){
    context.putImageData(e.state, 0, 0);
  }
}

做完啦!

https://i.imgur.com/GfY6CD9.gif

現在,我們可以利用瀏覽器的「上一頁」與「下一頁」來做到「復原」以及「取消復原」的功能囉!

附上 Demo 網址: https://bl.ocks.org/kurotanshi/6448231


  • [註]: 上面是對 HTML5 的 Canvas 與 History API 的綜合範例,實際上 pushState() 能存進的資料會依瀏覽器的實作而有所不同,像 Chrome 可以存到 10M,而 IE11 只有 1M,開發時要記得先確認對環境的支援程度。

  • MDN: Manipulating the browser history:

The state object can be anything that can be serialized. Because Firefox saves state objects to the user's disk so they can be restored after the user restarts the browser, we impose a size limit of 640k characters on the serialized representation of a state object. If you pass a state object whose serialized representation is larger than this to pushState(), the method will throw an exception. If you need more space than this, you're encouraged to use sessionStorage and/or localStorage.


上一篇
重新認識 JavaScript 番外篇 (1) - 實用小技巧 copy 事件
下一篇
重新認識 JavaScript 番外篇 (3) - 鐵人賽戰況分析
系列文
重新認識 JavaScript37
0
Ho.Chun
iT邦新手 5 級 ‧ 2019-01-28 00:08:17

太神奇啦!!! 感謝/images/emoticon/emoticon37.gif

0
Hank Hsiao
iT邦新手 5 級 ‧ 2019-02-13 13:07:19

Kuro 大大你好 最後一個上一頁復原的 Jsbin 網址好像錯了 沒有復原功能

Kuro Hsu iT邦新手 3 級 ‧ 2019-05-19 20:28:41 檢舉

啊,確實是我貼錯網址了 orz

這個版本是 ok 的喔,謝謝提醒 :D
https://bl.ocks.org/kurotanshi/6448231

0
阿展展展
iT邦好手 1 級 ‧ 2019-08-09 07:11:25

覺得有趣!!

我要留言

立即登入留言