[go: nahoru, domu]

Skip to content

dogezhou/web-whiteboard

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

web-whiteboard

whiteboard-screenshot

本项目用 js canvas 制作一个在线的画板

一、 鼠标事件

画板主要用鼠标进行交互,所以我们需要监听一些鼠标事件:
mousedown, mousemove, mouseup

  • 按下鼠标
document.addEventListener('mousedown', function(e) {
    console.log('按下鼠标', e)
})
  • 移动鼠标
document.addEventListener('mousemove', function(e) {
    console.log('移动鼠标', e)
    console.log('鼠标视口上的 X 坐标', e.clientX)
    console.log('鼠标视口上的 Y 坐标', e.clientY)
    console.log('鼠标相对于事件源x轴的位置', e.offsetX)
    console.log('鼠标相对于事件源y轴的位置', e.offsetY)
})
  • 松开鼠标
document.addEventListener('mousemove', function(e) {
    console.log('松开鼠标', e)
})

二、实现画线

监听了鼠标事件,就可以在按下鼠标然后移动鼠标的时候,操作 canvas 的像素实现画线的操作了。
Canvas API: 点击查看

1. 看文档,快速搞个 canvas demo:

<!-- canvas 元素和图片类似,用 css 操作他的大小会等比缩放,所以要直接设置他的大小 -->
<canvas id="canvas" width=400 height=400></canvas>
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

ctx.fillStyle = 'green';
ctx.fillRect(10, 10, 100, 100);

2. 怎么画线?

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

// 定义一个画点的函数
function drawDots(x, y, radius) {
    ctx.beginPath()
    ctx.arc(x, y, radius, 0, Math.PI * 2)
    ctx.fill()
}

// 鼠标操作
var enableDrag = false
canvas.addEventListener('mousedown', function(e) {
    enableDrag = true
    var x = e.offsetX
    var y = e.offsetY
    drawDots(x, y, 1)
})
canvas.addEventListener('mousemove', function(e) {
    if (enableDrag) {
        var x = e.offsetX
        var y = e.offsetY
        drawDots(x, y, 1)
    }
})
canvas.addEventListener('mouseup', function(e) {
    enableDrag = false
})

3. 画线的问题

上面的画线有一个问题就是,mousemove 事件的触发并不是每移动一个像素就触发一次的,浏览器有一定触发频率,所以按照上面的办法,鼠标移动快的时候线是不连续的。
这里的解决方案是把两次触发的 mousemove 的点用线连接起来,使用到的 canvas API 如下

// 画线的函数
function drawLine(x1, y1, x2, y2, width) {
    ctx.beginPath()
    ctx.moveTo(x1, y1)
    ctx.lineWidth = width
    ctx.lineTo(x2, y2)
    ctx.stroke()
    ctx.closePath()
}

修改 mousemove 连接当前点和之前点

var lastPoint = { x: undefined, y: undefined }
canvas.addEventListener('mousedown', function(e) {
    enableDrag = true
    var x = e.offsetX
    var y = e.offsetY
    // 在按下时就要更新 lastPoint, 不然下一次点击会连接到上一条线的 lastPoint
    lastPoint = { x: x, y: y }
    drawDots(x, y, 1)
})
canvas.addEventListener('mousemove', function(e) {
    if (enableDrag) {
        var x = e.offsetX
        var y = e.offsetY
        var newPoint = { x: x, y: y}
        drawDots(x, y, 1)
        drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y, 5)
        lastPoint = newPoint
    }
})

4. 画线的优化

我们已经连接了鼠标移动的当前点和之前点了,那么还需要再画出点来吗?不需要了。所以可以把 drawDots 函数都删了。

var lastPoint = { x: undefined, y: undefined }
canvas.addEventListener('mousedown', function(e) {
    enableDrag = true
    var x = e.offsetX
    var y = e.offsetY
    // 在按下时就要更新 lastPoint, 不然下一次点击会连接到上一条线的 lastPoint
    lastPoint = { x: x, y: y }
})
canvas.addEventListener('mousemove', function(e) {
    if (enableDrag) {
        var x = e.offsetX
        var y = e.offsetY
        var newPoint = { x: x, y: y}
        drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y, 5)
        lastPoint = newPoint
    }
})

三、Canvas 元素的全屏显示

Canvas 的大小取决于他的属性值,如果使用 CSS 设置宽高那么他会等比例缩放,还是包含那么像素,就像一个图片一样。所以要让 Canvas 全屏,需要设置它的属性为窗口大小。

function setCanvasSize() {
    var pageWidth = document.documentElement.clientWidth
    var pageHeight = document.documentElement.clientHeight
    canvas.width = pageWidth
    canvas.height = pageHeight
}
setCanvasSize()
// 改变窗口大小重新设置
window.onresize = function() {
    setCanvasSize()
}

四、橡皮擦功能

任何作品都不可能一次完成,我们需要一个橡皮擦。实现鼠标点按滑动擦掉内容。
Canvas 清除画板 API:

ctx.clearRect(x, y, 10, 10)

1. 添加橡皮擦按钮

<button id=eraser>橡皮擦</button>
#eraser {
    position: fixed;
    bottom: 10px;
    left: 10px;
}
var eraserEnabled = false
var eraser = document.getElementById('eraser');
eraser.addEventListener('click', function() {
    eraserEnabled = !eraserEnabled
})

2. 实现橡皮擦功能

需要修改画线的地方

canvas.addEventListener('mousedown', function(e) {
    enableDrag = true
    var x = e.offsetX
    var y = e.offsetY
    if (eraserEnabled) {
        ctx.clearRect(x, y, 10, 10)
    } else {
        // 在按下时就要更新 lastPoint, 不然下一次点击会连接到上一条线的 lastPoint
        lastPoint = { x: x, y: y }
    }
})
canvas.addEventListener('mousemove', function(e) {
    if (!enableDrag) {
        return
    }

    var x = e.offsetX
    var y = e.offsetY
    if (eraserEnabled) {
        ctx.clearRect(x, y, 10, 10)
    } else {
        var newPoint = { x: x, y: y}
        drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y, 5)
        lastPoint = newPoint
    }
})

3. 橡皮擦的细节错误

由于 ctx.clearRect 的清除的是以 x, y 为左上角的正方形,所以用起来就像以鼠标为左上角,优化一下就要

ctx.clearRect(x - 5, y - 5, 10, 10)

五、移动端支持

1. 移动端检测

移动端没有 mouse 事件,所以应该使用对应的 touchstart, touchmove, touchend 事件。但是如何同时支持PC 和移动端呢?那就需要检测当前页面是否支持 touch 事件,如果支持就使用监听 touch,没有就 mouse。

document.body.ontouchstart === undefined

注意没有直接判断是手机还是PC,直接检测了是否支持触屏,因为现在有很多二合一的设备,所有特性检测就行了。

2. 多点触控处理

TouchEvent 中的 touches 收集了用户多点触控的信息,直接使用第一个点

canvas.addEventListener('touchstart', function (e) {
    var x = e.touches[0].clientX
    var y = e.touches[0].clientY
})

3. 浏览器下拉刷新无法画画的问题

可以阻止 touch 事件的传播 参考这篇文章

Releases

No releases published

Packages

No packages published