iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 17
2
Modern Web

Fabricjs 筆記系列 第 17

Day 17 - 簡易小畫家實作

  • 分享至 

  • xImage
  •  

今天就來複習昨天我們學到的 Fabricjs 繪畫功能來做一個簡易的小畫家吧。

透過 Fabricjs 我們可以相當快速又簡單的做出一個類似畫板的功能,讓使用者能夠隨意的切換畫筆顏色、粗細、陰影等樣式。

最後會在特別介紹 Fabricjs 將 canvas 轉換成圖片的功能。

先來看看做出來的結果

製作過程

  1. 將 HTML 按鈕和控制項產生出來,幫每個按鈕設定 id 讓我們之後可以控制,並且存取他們。
const $ = (id) => document.getElementById(id)
const drawingOptionArea = $('drawingOptionArea')
const clearBtn = $('clear')
const modeBtn = $('mode')
const lineWidthInput = $('lineWidthInput')
const lineWidthValue = $('lineWidthValue')
const lineColorInput = $('lineColorInput')
...略
  1. 寫出控制 canvas 事件要執行的 function,透過控制我們所建立的 canvas 實例來操控筆刷樣式和切換模式,透過昨天我們所練習的操作畫筆樣式的操作。
function clearCanvas () {
  canvas.clear()
}

function toggleMode () {
  canvas.isDrawingMode = !canvas.isDrawingMode
  if (!canvas.isDrawingMode) {
    modeBtn.innerHTML = '切換成畫筆模式'
    drawingOptionArea.style.display = 'none'
  } else {
    modeBtn.innerHTML = '切換成物件模式'
    drawingOptionArea.style.display = ''
  }
}

function changeLineColor () {
  canvas.freeDrawingBrush.color = this.value
}

function changeShadowBlur () {
  myShadow.blur = this.value
  canvas.freeDrawingBrush.setShadow(myShadow)
  shadowBlurValue.innerHTML = this.value
}

...略
  1. 綁定按鈕、input 控制項事件,和我們寫好的 function 結合,做出對應的動作
mode.addEventListener('click', toggleMode)
lineWidthInput.addEventListener('change', changeLineWidth)
lineColorInput.addEventListener('change', changeLineColor)
shadowColorInput.addEventListener('change', changeShadowColor)
shadowBlurInput.addEventListener('change', changeShadowBlur)
...略

匯出功能

今天有做了一個之前沒有介紹到的新功能,也就是使用者繪製完後,最後能夠直接的匯出成圖檔的功能。

Fabricjs 主要能將 canvas 匯出成 jpeg 和 png 格式。

若使用 jpeg 格式能夠調整整體輸出的畫質。

canvas.toDataURL

透過 canvas.toDataURL 能夠輕鬆地將 canvas 轉成 Base64 的編碼,再透過瀏覽器下載到本機,且可傳入一些基本設定值。

options

  • format : 'image/png' | 'image/jpeg'
    選擇要輸出成 png or jpeg 檔,這邊須注意到我自己在做的時候,原本只使用 'png' 和 'jpeg'作為參數,但是這樣輸出 jpeg 的 Base64 編碼會有點問題,需要在前面加上 'image/' 當作前墜,也就是 'image/png' | 'image/jpeg' 這樣輸出就沒有問題了。
  • top、left、width、height 這幾個參數用來設定你要輸出畫布的大小
  • multiplier 這個參數可以縮放你所輸出的 canvas ,參數為縮放倍率 ex: 0.5 輸出大小為原本一半
  • quality 可以調整輸出圖片的質量,範圍為 0..~1
function output (formatType) {
  const dataURL = canvas.toDataURL({
    format: `image/${formatType}`,
    top: 0,
    left: 0,
    width: window.innerWidth,
    height: window.innerHeight,
    multiplier: 0.5,
    quality: 0.1
  })
  const a = document.createElement('a')
  a.href = dataURL
  a.download = `output.${formatType}`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
}

完整程式

HTML

<canvas id="canvas"></canvas>
<div class="options">
  <div>
    <button id="mode" >切換成物件模式</button>
    <button id="clear">清除</button>
    <button id="outputJpgBtn">匯出成 jpeg</button>
    <button id="outputPngBtn">匯出成 png</button>

  </div>
  <div id="drawingOptionArea">
    <div>
      <label>粗度:</label>
      <input type="range" min="0" max="150" id="lineWidthInput" value="1"> 
      <span id="lineWidthValue">1</span>
    </div>
    <div>
      <label>顏色:</label>
      <input type="color" min="0" max="150" id="lineColorInput">  
    </div>
    <div>
      <label>陰影顏色:</label>
      <input type="color" min="0" max="150" id="shadowColorInput">  
    </div>
    <div>
      <label>陰影模糊:</label>
      <input type="range" min="0" max="30" id="shadowBlurInput" value="1">
      <span id="shadowBlurValue">1</span>
    </div>
    <div>
      <label>陰影 X 軸 偏移:</label>
      <input type="range" min="0" max="50" id="shadowOffsetXInput" value="1"> 
      <span id="shadowOffsetXValue">1</span>
    </div>
    <div>
      <label>陰影 Y 軸 偏移:</label>
      <input type="range" min="0" max="50" id="shadowOffsetYInput" value="1"> 
      <span id="shadowOffsetYValue">1</span>
    </div>
  </div>
</div>

css

canvas {
  border: 1px solid #000;
}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
label {
  display: inline-block;
  width: 200px;
  color: white;
}
#drawingOptionArea {
  width: 380px;
}
#drawingOptionArea > div {
  background-color: rgba(0,0,0,0.5);
  border-bottom: 1px solid rgba(255,255,255,0.1)
}
.options {
  position: absolute;
  top: 0;
  left: 0;
  background-color: #878787;
}

span {
  color: #fff
}

javascript

const canvas = new fabric.Canvas('canvas', {
  width: window.innerWidth,
  height: window.innerHeight
})
const myShadow = {
  color: 'black',
  blur: 1,
  offsetX: 1,
  offsetY: 1
}

const $ = (id) => document.getElementById(id)
const drawingOptionArea = $('drawingOptionArea')
const clearBtn = $('clear')
const modeBtn = $('mode')
const lineWidthInput = $('lineWidthInput')
const lineWidthValue = $('lineWidthValue')
const lineColorInput = $('lineColorInput')
const shadowColorInput = $('shadowColorInput')
const shadowBlurInput = $('shadowBlurInput')
const shadowBlurValue = $('shadowBlurValue')
const shadowOffsetXInput = $('shadowOffsetXInput')
const shadowOffsetXValue = $('shadowOffsetXValue')
const shadowOffsetYInput = $('shadowOffsetYInput')
const shadowOffsetYValue = $('shadowOffsetYValue')
const outputJpegBtn = $('outputJpgBtn')
const outputPngBtn = $('outputPngBtn')
const brushSelector = $('brushSelect')

function toggleMode () {
  canvas.isDrawingMode = !canvas.isDrawingMode
  if (!canvas.isDrawingMode) {
    modeBtn.innerHTML = '切換成畫筆模式'
    drawingOptionArea.style.display = 'none'
  } else {
    modeBtn.innerHTML = '切換成物件模式'
    drawingOptionArea.style.display = ''
  }
}

function changeLineWidth () {
  const newWidth = parseInt(this.value, 10) || 1
  canvas.freeDrawingBrush.width = newWidth
  lineWidthValue.innerHTML = newWidth
}

function changeLineColor () {
  canvas.freeDrawingBrush.color = this.value
}

function changeShadowBlur () {
  myShadow.blur = this.value
  canvas.freeDrawingBrush.setShadow(myShadow)
  shadowBlurValue.innerHTML = this.value
}

function changeShadowColor () {
  myShadow.color = this.value
  canvas.freeDrawingBrush.setShadow(myShadow)
}

function changeShadowOffsetX () {
  myShadow.offsetX = this.value
  canvas.freeDrawingBrush.setShadow(myShadow)
  shadowOffsetXValue.innerHTML = this.value

}

function changeShadowOffsetY () {
  myShadow.offsetY = this.value
  canvas.freeDrawingBrush.setShadow(myShadow)
  shadowOffsetYValue.innerHTML = this.value
}

function changeShadowColor () {
  myShadow.color = this.value
  canvas.freeDrawingBrush.setShadow(myShadow)
  canvas.freeDrawingBrush.shadow.color = this.value
}

function clearCanvas () {
  canvas.clear()
}

function output (formatType) {
  const dataURL = canvas.toDataURL({
    format: `image/${formatType}`,
    top: 0,
    left: 0,
    width: window.innerWidth,
    height: window.innerHeight,
    multiplier: 1,
    quality: 0.1
  })
  const a = document.createElement('a')
  a.href = dataURL
  a.download = `output.${formatType}`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
}

function selectBrush () {
  if (this.value === 'Square') {
    const squareBrush = new fabric.PatternBrush(canvas)
    // getPatternSrc  取得要重複繪製的圖形 Canvas
    squareBrush.getPatternSrc = function() {
      const squareWidth = 30
      const squareDistance = 2
      // 創立一個暫存 canvas 來繪製要畫的圖案
      const patternCanvas = fabric.document.createElement('canvas')
      // canvas 總大小為每一格畫筆的大小
      patternCanvas.width = patternCanvas.height = squareWidth + squareDistance
      const ctx = patternCanvas.getContext('2d')
      ctx.fillStyle = this.color
      ctx.fillRect(0, 0, squareWidth, squareWidth)
      // 回傳繪製完畢的 canvas
      return patternCanvas
    }

    canvas.freeDrawingBrush = squareBrush
  } else {
    canvas.freeDrawingBrush = new fabric[this.value + 'Brush'](canvas)
  }
  canvas.freeDrawingBrush.color = lineColorInput.value
  canvas.freeDrawingBrush.width = parseInt(lineWidthInput.value, 10) || 1
  canvas.freeDrawingBrush.setShadow(myShadow)

}

mode.addEventListener('click', toggleMode)
lineWidthInput.addEventListener('change', changeLineWidth)
lineColorInput.addEventListener('change', changeLineColor)
shadowColorInput.addEventListener('change', changeShadowColor)
shadowBlurInput.addEventListener('change', changeShadowBlur)
shadowOffsetXInput.addEventListener('change', changeShadowOffsetX)
shadowOffsetYInput.addEventListener('change', changeShadowOffsetY)
clearBtn.addEventListener('click', clearCanvas)
outputJpegBtn.addEventListener('click', () => output('jpeg'))
outputPngBtn.addEventListener('click', () => output('png'))
brushSelector.addEventListener('change', selectBrush)
canvas.isDrawingMode = true

本日小結

將昨天所學的 fabricjs 筆刷功能,配合 html input 來控制畫筆樣式。

將畫完的結果匯出成圖檔。匯出圖檔的詳細說明。


上一篇
Day 16 - Fabricjs 繪畫功能介紹
下一篇
Day 18 - Fabricjs 實作: 圖片上傳並透過拖曳進入 canvas
系列文
Fabricjs 筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言