之前学了 canvas,当初线上签字那么风行,就想着本人实现一个玩玩

打算实现一个蕴含以下性能的写字板

  • 应用鼠标写字
  • 调整画笔粗细
  • 批改画笔色彩
  • 撤销性能
  • 清空画布的性能
  • 生成图片,下载签名

动态构造

动态构造很简略,就间接列出来不做解释了

HTML

<div>    <div id="settings">        <div>            字体大小:            <select id="fontSize"></select>        </div>        <div>            字体色彩:            <input type="color" id="fontColor" />        </div>        <button id="undo">撤销</button>        <button id="clear">清空画布</button>        <button id="download">生成图片</button>    </div>    <canvas width="800" height="300" id="tablet"></canvas></div>

CSS

#settings {    display: flex;    margin: auto;    margin-top: 100px;    line-height: 27px;    justify-content: center;    gap: 20px;}#tablet {    display: block;    margin: auto;    margin-top: 10px;    background-color: #eee;}

gap 属性可能有人不意识,能够设置 flex/grad 布局子元素的距离

动态页面就是这么个样子,字体大小的选项是用 js 生成的

接下来咱们来写 JS

性能实现

初始化 canvas

咱们先初始化 canvas 和其 2d 上下文 ctx

设置线宽为 5,线的末端和连接处均为圆角

const tablet = document.getElementById('tablet')const ctx = tablet.getContext('2d')ctx.lineWidth = 5ctx.lineCap = 'round'ctx.lineJoin = 'round'

字体大小

而后咱们生成字体大小的选项,容许 1-16px,默认为 5px

const fontSize = document.getElementById('fontSize')for (let i = 1; i <= 16; i++) {    const option = document.createElement('option')    option.value = i    option.innerText = i + 'px'    fontSize.appendChild(option)    if (i == 5) {        option.selected = true    }}fontSize.addEventListener('change', (e) => {    ctx.lineWidth = e.target.value})

字体色彩

从 input 读取色彩,设置线色

const fontColor = document.getElementById('fontColor')fontColor.addEventListener('change', (e) => {    ctx.strokeStyle = e.target.value})

写字

写字分为三局部

  • 鼠标落下:开始写字,对应 mousedown 事件
  • 鼠标挪动:正在写字,对应 mousemove 事件
  • 鼠标抬起:完结写字,对应 mouseup 事件

写字无非就是许多点连成的线,在写字过程中,记录鼠标上一点的地位,将其与以后点连起来

我集体习惯在鼠标按下时注册事件,鼠标松开时革除事件;还有一种实现形式是用一个变量管制 mouseover 是否执行

mouseup 事件绑定在 document 上,又加了一个 mouseenter 事件,使得鼠标来到画布再回来能够持续写。

let prex, prey // 记录上一点的地位const mousedown = (e) => {    if (e.button != 0) return // 不是左键间接返回    prex = e.offsetX    prey = e.offsetY    ctx.beginPath() // 开始门路    ctx.moveTo(prex, prey)    // 注册事件    tablet.addEventListener('mousemove', mousemove)    document.addEventListener('mouseup', mouseup)    tablet.addEventListener('mouseenter', mouseenter)}const mousemove = (e) => {    ctx.lineTo(e.offsetX, e.offsetY)    ctx.stroke()}const mouseup = () => {    // 敞开门路    ctx.closePath()    // 革除事件    tablet.removeEventListener('mousemove', mousemove)    document.removeEventListener('mouseup', mouseup)    tablet.removeEventListener('mouseenter', mouseenter)}const mouseenter = (e) => {    // 从内部回到画布,扭转上一点的地位,持续书写    prex = e.offsetX    prey = e.offsetY    ctx.moveTo(prex, prey)}tablet.addEventListener('mousedown', mousedown)

撤销

想要实现撤销性能,就要保留之前的状态,能够在开始写字时保留此时的画布状态,在须要撤销时复原

思考到 imageData 内容较大,咱们这里只实现了一步的撤销性能

let imageData // 图片数据const mousedown = (e) => {    imageData = ctx.getImageData(0, 0, tablet.width, tablet.height)    ……}const undo = document.getElementById('undo')undo.addEventListener('click', () => {    ctx.putImageData(imageData, 0, 0)})

清空画布

清空画布很简略,clearRect 就好了

const clear = document.getElementById('clear')clear.addEventListener('click', () => {    ctx.clearRect(0, 0, tablet.width, tablet.height)})

下载图片

canvas 生成 DateURL,而后利用 a 标签下载

let aconst download = document.getElementById('download')download.addEventListener('click', () => {    if (!a) {        a = document.createElement('a')    }    a.href = tablet.toDataURL('image/png')    a.download = '签名.png'    a.click()})

残缺代码

<!DOCTYPE html><html lang="zh-cmn-Hans"><head>    <meta charset="UTF-8" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <title>Document</title>    <style type="text/css">        #settings {            display: flex;            margin: auto;            margin-top: 100px;            line-height: 27px;            justify-content: center;            gap: 20px;        }        #tablet {            display: block;            margin: auto;            margin-top: 10px;            background-color: #eee;        }    </style></head><body>    <div>        <div id="settings">            <div>                字体大小:                <select id="fontSize"></select>            </div>            <div>                字体色彩:                <input type="color" id="fontColor" />            </div>            <button id="undo">撤销</button>            <button id="clear">清空画布</button>            <button id="download">生成图片</button>        </div>        <canvas width="800" height="300" id="tablet"></canvas>    </div>    <script>        const tablet = document.getElementById('tablet')        const ctx = tablet.getContext('2d')        ctx.lineCap = 'round'        ctx.lineJoin = 'round'        ctx.lineWidth = 5        ctx.strokeStyle = '#000'        // 生成字体大小选项        const fontSize = document.getElementById('fontSize')        for (let i = 1; i <= 16; i++) {            const option = document.createElement('option')            option.value = i            option.innerText = i + 'px'            if (i == 5) {                option.selected = true            }            fontSize.appendChild(option)        }        fontSize.addEventListener('change', (e) => {            ctx.lineWidth = e.target.value        })        const fontColor = document.getElementById('fontColor')        fontColor.addEventListener('change', (e) => {            ctx.strokeStyle = e.target.value        })        let imageData        let prex, prey        const mousedown = (e) => {            if (e.button != 0) return // 不是左键间接返回            imageData = ctx.getImageData(0, 0, tablet.width, tablet.height)            prex = e.offsetX            prey = e.offsetY            ctx.beginPath()            ctx.moveTo(prex, prey)            tablet.addEventListener('mousemove', mousemove)            document.addEventListener('mouseup', mouseup)            tablet.addEventListener('mouseenter', mouseenter)        }        const mousemove = (e) => {            ctx.lineTo(e.offsetX, e.offsetY)            ctx.stroke()        }        const mouseup = () => {            tablet.removeEventListener('mousemove', mousemove)            document.removeEventListener('mouseup', mouseup)            tablet.removeEventListener('mouseenter', mouseenter)        }        const mouseenter = (e) => {            prex = e.offsetX            prey = e.offsetY            ctx.moveTo(prex, prey)        }        tablet.addEventListener('mousedown', mousedown)        const undo = document.getElementById('undo')        undo.addEventListener('click', () => {            ctx.putImageData(imageData, 0, 0)        })        const clear = document.getElementById('clear')        clear.addEventListener('click', () => {            ctx.clearRect(0, 0, tablet.width, tablet.height)        })        let a        const download = document.getElementById('download')        download.addEventListener('click', () => {            if (!a) {                a = document.createElement('a')            }            a.href = tablet.toDataURL()            a.download = '签名.png'            a.click()        })    </script></body></html>

结语

至此写字板就实现了,都是 canvas 的常识,最初借助了 a 标签实现图片下载。

如果喜爱或有所帮忙的话,心愿能点赞关注,激励一下作者。

如果文章有不正确或存疑的中央,欢送评论指出。