手写 Canvas 画板

Huy大约 5 分钟javascriptjavascript

本文简单手写一个 Canvas 画板,效果如下:

Canvas 画板
Canvas 画板

功能点:

  1. 更改画笔颜色;
  2. 更改画笔大小;
  3. 清空画布。

理清思路

  1. 获取画布元素;
  2. 获取画笔颜色、画笔大小;
  3. 获取画布上下文;
  4. 监听鼠标按下事件;
  5. 监听鼠标移动事件;
  6. 监听鼠标抬起事件;
  7. 鼠标按下时,记录鼠标按下时的坐标;
  8. 鼠标移动时,根据鼠标按下时的坐标和移动时的坐标,绘制出一条线段;
  9. 鼠标抬起时,结束绘制。

代码实现

绘制 UI

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Canvas 画板</title>
    <style>
      body {
        margin: 0;
      }
      .container {
        width: 1024px;
        height: 600px;
        margin: 50px auto 0;
        box-shadow: 0 0 10px #000;
        border-radius: 10px;
        overflow: hidden;
        cursor: crosshair;
      }

      /* 工具栏 */
      .tool-bar {
        width: 1024px;
        height: 80px;
        margin: 18px auto;
        border-radius: 5px;
        display: flex;
        align-items: center;
        /* background-color: #8b8888;  */
      }

      .tool-bar div {
        width: 78px;
        height: 78px;
        border: 1px solid #b3b2b2;
        margin: 0 2px;
        border-radius: 50%;
        box-sizing: border-box;
        cursor: pointer;
      }

      .tool-bar .black {
        background-color: #000;
      }
      .tool-bar .red {
        background-color: #f00;
      }
      .tool-bar .green {
        background-color: #0f0;
      }
      .tool-bar .blue {
        background-color: #00f;
      }

      .tool-bar .line {
        display: flex;
        justify-content: center;
        align-items: center;
      }
      .tool-bar .line span {
        display: block;
        border-radius: 50%;
        background-color: #000000;
      }
      .tool-bar .line-4 span {
        width: 4px;
        height: 4px;
      }
      .tool-bar .line-8 span {
        width: 8px;
        height: 8px;
      }
      .tool-bar .line-12 span {
        width: 12px;
        height: 12px;
      }

      .tool-bar .remove {
        text-align: center;
        line-height: 78px;
        font-size: 40;
      }
    </style>
  </head>
  <body>
    <!-- 画板主体 -->
    <div class="container">
      <canvas id="myCanvas" class="canvas" width="1024" height="600" />
    </div>

    <!-- 工具栏 -->
    <div class="tool-bar">
      <div class="color black" data-color="black"></div>
      <div class="color red" data-color="red"></div>
      <div class="color green" data-color="green"></div>
      <div class="color blue" data-color="blue"></div>
      <div class="line line-4" data-line="4">
        <span></span>
      </div>
      <div class="line line-8" data-line="8">
        <span></span>
      </div>
      <div class="line line-12" data-line="12">
        <span></span>
      </div>
      <div class="remove">擦除</div>
    </div>
  </body>
</html>

编写插件事件

在写插件时,分为俩个步骤:

  1. 初始化插件;
  2. 绑定监听事件;

初始化插件

初始化插件,包含初始化数据和定义所需要的监听事件。

监听事件包含,点击事件、移动事件,抬起事件。而事件监听都放在 bindEvent 函数中。

;(function () {
  // 获取元素
  var can = document.getElementById('myCanvas')
  var tools = document.getElementsByClassName('tool-bar')[0]

  // 初始化数据
  var ctx = can.getContext('2d')
  var cWidth = can.width
  var cHeight = can.height
  var color = 'block'
  var lineWidth = 4
  var x = 0
  var y = 0

  /** 定义初始化函数 */
  var init = function () {
    bindEvent()
  }

  /** 绑定事件 */
  function bindEvent() {}

  /** 鼠标点击 */
  function mouseDown(e) {}

  /** 移动鼠标 */
  function mouseMove(e) {}

  /** 鼠标抬起 */
  function mouseUp(e) {}

  /** 点击工具栏 */
  function clickTool(e) {}

  init()
})()

完善监听事件

思路为:

  1. 获取 Canvas 上下文;
  2. 调用 beginPath 开始新的绘制;
  3. 调用 moveTo 将画笔移动到鼠标位置;
  4. 持续监听鼠标移动位置, 并调用 lineTo 绘制线条;
  5. 调用 stroke 完成绘制,并在最后鼠标抬起时,清空监听事件。
;(function () {
  var can = document.getElementById('myCanvas')
  var tools = document.getElementsByClassName('tool-bar')[0]

  var ctx = can.getContext('2d')
  var cWidth = can.width
  var cHeight = can.height
  var color = 'block'
  var lineWidth = 4
  var x = 0
  var y = 0

  var init = function () {
    bindEvent()
  }

  /** 绑定事件 */
  function bindEvent() {
    // 画布事件
    can.addEventListener(
      'mousedown',
      function (e) {
        mouseDown(e)

        document.addEventListener('mousemove', mouseMove, false)
        document.addEventListener('mouseup', mouseUp, false)
      },
      false
    )

    // 工具栏事件
    tools.addEventListener('click', clickTool, false)
  }

  /** 鼠标点击 */
  function mouseDown(e) {
    setCanXY(e)

    ctx.beginPath() // 开始新的绘制,避免干扰之前已有的绘图
    ctx.strokeStyle = color
    ctx.lineWidth = lineWidth
    ctx.lineCap = 'round'
    ctx.lineJoin = 'round'
    ctx.moveTo(x, y)
  }

  /** 移动鼠标 */
  function mouseMove(e) {
    setCanXY(e)

    ctx.lineTo(x, y)
    ctx.stroke()
  }

  /** 鼠标抬起 */
  function mouseUp(e) {
    document.removeEventListener('mousemove', mouseMove, false)
    document.removeEventListener('mouseup', mouseUp, false)
  }

  /** 设置落笔位置 */
  function setCanXY(e) {
    var e = e || window.event

    var xPos = e.clientX - can.offsetLeft
    var yPos = e.clientY - can.offsetTop
    // 边界限制
    x = xPos < 0 ? 0 : xPos > cWidth ? cWidth : xPos
    y = yPos < 0 ? 0 : yPos > cHeight ? cHeight : yPos
  }

  /** 点击工具栏 */
  function clickTool(e) {
    var e = e || window.event
    var tar = e.target || e.srcElement

    // 检查是否点击了颜色工具
    if (tar.classList.contains('color')) {
      color = tar.getAttribute('data-color')
    }

    // 检查是否点击了线宽工具
    if (tar.classList.contains('line')) {
      lineWidth = tar.getAttribute('data-line')
    }

    // 检查是否点击了擦除工具
    if (tar.classList.contains('remove')) {
      ctx.clearRect(0, 0, cWidth, cHeight)
    }
  }

  init()
})()

完整插件代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Canvas 画板</title>
    <style>
      body {
        margin: 0;
      }
      .container {
        width: 1024px;
        height: 600px;
        margin: 50px auto 0;
        box-shadow: 0 0 10px #000;
        border-radius: 10px;
        overflow: hidden;
        cursor: crosshair;
      }

      /* 工具栏 */
      .tool-bar {
        width: 1024px;
        height: 80px;
        margin: 18px auto;
        border-radius: 5px;
        display: flex;
        align-items: center;
        /* background-color: #8b8888;  */
      }

      .tool-bar div {
        width: 78px;
        height: 78px;
        border: 1px solid #b3b2b2;
        margin: 0 2px;
        border-radius: 50%;
        box-sizing: border-box;
        cursor: pointer;
      }

      .tool-bar .black {
        background-color: #000;
      }
      .tool-bar .red {
        background-color: #f00;
      }
      .tool-bar .green {
        background-color: #0f0;
      }
      .tool-bar .blue {
        background-color: #00f;
      }

      .tool-bar .line {
        display: flex;
        justify-content: center;
        align-items: center;
      }
      .tool-bar .line span {
        display: block;
        border-radius: 50%;
        background-color: #000000;
      }
      .tool-bar .line-4 span {
        width: 4px;
        height: 4px;
      }
      .tool-bar .line-8 span {
        width: 8px;
        height: 8px;
      }
      .tool-bar .line-12 span {
        width: 12px;
        height: 12px;
      }

      .tool-bar .remove {
        text-align: center;
        line-height: 78px;
        font-size: 40;
      }
    </style>
  </head>
  <body>
    <!-- 画板主体 -->
    <div class="container">
      <canvas id="myCanvas" class="canvas" width="1024" height="600" />
    </div>

    <!-- 工具栏 -->
    <div class="tool-bar">
      <div class="color black" data-color="black"></div>
      <div class="color red" data-color="red"></div>
      <div class="color green" data-color="green"></div>
      <div class="color blue" data-color="blue"></div>
      <div class="line line-4" data-line="4">
        <span></span>
      </div>
      <div class="line line-8" data-line="8">
        <span></span>
      </div>
      <div class="line line-12" data-line="12">
        <span></span>
      </div>
      <div class="remove">擦除</div>
    </div>

    <script>
      ;(function () {
        var can = document.getElementById('myCanvas')
        var tools = document.getElementsByClassName('tool-bar')[0]

        var ctx = can.getContext('2d')
        var cWidth = can.width
        var cHeight = can.height
        var color = 'block'
        var lineWidth = 4
        var x = 0
        var y = 0

        var init = function () {
          bindEvent()
        }

        /** 绑定事件 */
        function bindEvent() {
          // 画布事件
          can.addEventListener(
            'mousedown',
            function (e) {
              mouseDown(e)

              document.addEventListener('mousemove', mouseMove, false)
              document.addEventListener('mouseup', mouseUp, false)
            },
            false
          )

          // 工具栏事件
          tools.addEventListener('click', clickTool, false)
        }

        /** 鼠标点击 */
        function mouseDown(e) {
          setCanXY(e)

          ctx.beginPath() // 开始新的绘制,避免干扰之前已有的绘图
          ctx.strokeStyle = color
          ctx.lineWidth = lineWidth
          ctx.lineCap = 'round'
          ctx.lineJoin = 'round'
          ctx.moveTo(x, y)
        }

        /** 移动鼠标 */
        function mouseMove(e) {
          setCanXY(e)

          ctx.lineTo(x, y)
          ctx.stroke()
        }

        /** 鼠标抬起 */
        function mouseUp(e) {
          document.removeEventListener('mousemove', mouseMove, false)
          document.removeEventListener('mouseup', mouseUp, false)
        }

        /** 设置落笔位置 */
        function setCanXY(e) {
          var e = e || window.event

          var xPos = e.clientX - can.offsetLeft
          var yPos = e.clientY - can.offsetTop
          // 边界限制
          x = xPos < 0 ? 0 : xPos > cWidth ? cWidth : xPos
          y = yPos < 0 ? 0 : yPos > cHeight ? cHeight : yPos
        }

        /** 点击工具栏 */
        function clickTool(e) {
          var e = e || window.event
          var tar = e.target || e.srcElement

          // 检查是否点击了颜色工具
          if (tar.classList.contains('color')) {
            color = tar.getAttribute('data-color')
          }

          // 检查是否点击了线宽工具
          if (tar.classList.contains('line')) {
            lineWidth = tar.getAttribute('data-line')
          }

          // 检查是否点击了擦除工具
          if (tar.classList.contains('remove')) {
            ctx.clearRect(0, 0, cWidth, cHeight)
          }
        }

        init()
      })()
    </script>
  </body>
</html>
Loading...