console
const WIDTH = 400
const HEIGHT = 400
const DEVICEPIXELRATIO = window.devicePixelRatio
const global = (() => {
const canvas = document.getElementById('canvas')
canvas.style.width = `${WIDTH}px`
canvas.style.height = `${HEIGHT}px`
const ctx = canvas.getContext('2d')
canvas.width = WIDTH * DEVICEPIXELRATIO
canvas.height = HEIGHT * DEVICEPIXELRATIO
const layers = []
return {
canvas,
ctx,
layers
}
})()
function renderAll() {
global.ctx.clearRect(0, 0, global.canvas.width, global.canvas.height)
global.layers.forEach(layer => {
layer.render()
})
drawShapesBorder()
}
class IText {
constructor(x, y, text) {
this.x = x
this.y = y
this.text = text
this.color = '#ff00ff'
}
setColor(color) {
this.color = color
}
render() {
global.ctx.save()
global.ctx.font = '36px sans-serif'
global.ctx.fillStyle = this.color
global.ctx.fillText(this.text, this.x, this.y)
global.ctx.restore()
}
}
class Line {
constructor(x1, y1, x2, y2) {
this.x1 = x1
this.y1 = y1
this.x2 = x2
this.y2 = y2
this.color = '#ff00ff'
}
setColor(color) {
this.color = color
}
render() {
global.ctx.save()
global.ctx.strokeStyle = this.color
global.ctx.lineWidth = 3
global.ctx.beginPath()
global.ctx.moveTo(this.x1, this.y1)
global.ctx.lineTo(this.x2, this.y2)
global.ctx.stroke()
global.ctx.restore()
}
}
function throttle(fn, delay) {
let lastTime = 0
return (...arg) => {
const now = Date.now()
if (now - lastTime >= delay) {
fn.apply(null, arg)
lastTime = now
}
}
}
function drawShapesBorder() {
const { data: imageData = [] } = global.ctx.getImageData(0, 0, global.canvas.width, global.canvas.height)
let minX = minY = Number.MAX_SAFE_INTEGER
let maxX = maxY = -Number.MAX_SAFE_INTEGER
const alphas = []
for (let i = 0; i < imageData.length; i+=4) {
const alpha = imageData[i + 3]
alphas.push(alpha)
if (alpha > 0) {
const [x, y] = getPixelPointToCoord(i + 4)
minX = Math.min(minX, x)
minY = Math.min(minY, y)
maxX = Math.max(maxX, x)
maxY = Math.max(maxY, y)
}
}
global.ctx.strokeStyle = '#00ff00'
global.ctx.strokeRect(minX, minY, maxX - minX, maxY - minY)
}
function getPixelPointToCoord(index) {
const rowWidth = global.canvas.width
const rowPixelCount = rowWidth * 4
const x = (index % rowPixelCount) / 4
const y = parseFloat(index / rowPixelCount)
return [x, y]
}
function testHit(cssX, cssY, tolerance = 2) {
const pixelX = cssX * DEVICEPIXELRATIO
const pixelY = cssY * DEVICEPIXELRATIO
const rowWidth = global.canvas.width
const rowCount = rowWidth
const index = (pixelY - 1) * rowCount + pixelX - 1
const { data: imageData = [] } = global.ctx.getImageData(0, 0, global.canvas.width, global.canvas.height)
const filterCells = []
for (let i = -(tolerance + 1) * DEVICEPIXELRATIO + 1; i <= tolerance * DEVICEPIXELRATIO; i++ ) {
filterCells.push(i)
}
const filterRowsIndexs = filterCells.map(g => index + g * rowCount)
for (let i = 0; i < filterRowsIndexs.length; i++) {
for (let j = 0; j < filterCells.length; j++) {
const cellIndex = (filterRowsIndexs[i] + filterCells[j]) * 4 + 3
if (imageData[cellIndex] > 0) return true
}
}
return false
}
function addHitShape() {
const highLightColor = '#0000ff'
global.canvas.addEventListener('mousemove', throttle(e => {
const { offsetX, offsetY } = e
const isHover = testHit(offsetX, offsetY)
let isUpdate = false
global.layers.forEach(layer => {
const oldColor = layer.color
layer.setColor(isHover ? '#0000ff' : '#ff00ff')
if (oldColor !== layer.color) isUpdate = true
})
if (isUpdate) {
renderAll()
}
}, 10))
}
const iText = new IText(global.canvas.width / 2, global.canvas.height / 2, 'Text 文字')
const line = new Line(global.canvas.width / 2, global.canvas.height / 2, global.canvas.width / 2 - 150, global.canvas.height / 2 + 200)
global.layers.push(iText)
global.layers.push(line)
renderAll()
addHitShape()
<canvas id="canvas"></canvas>
#canvas {
position: absolute;
left: 50%;
transform: translateX(-50%);
cursor: pointer;
border: 1px solid #ddd
}