关于前端:用-Canvas-实现个手写板

30次阅读

共计 5308 个字符,预计需要花费 14 分钟才能阅读完成。

之前学了 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 = 5
ctx.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 a
const 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 标签实现图片下载。

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

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

正文完
 0