Fabric.js简易小画家实作

2018-11-27 23:33:56
1441次阅读
0个评论
今天就来复习昨天我们学到的 Fabricjs 绘画功能来做一个简易的小画家吧。

透过 Fabricjs 我们可以相当快速又简单的做出一个类似画板的功能,让使用者能够随意的切换画笔顏色、粗细、阴影等样式。

最后会在特别介绍 Fabricjs 将 canvas 转换成图片的功能。

先来看看做出来的结果


製作过程

将 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')
...略

写出控制 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
}

...略

绑定按钮、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

收藏00

登录 后评论。没有帐号? 注册 一个。