canvas 选取一个物体
在普通画布上选取
想要在canvas上选择一个物体,首先需要鼠标在canvas的坐标
,然后需要一个方法来判断鼠标坐标是否落在目标物体内.
首先我们先画一个圆
// app.js let canvas = document.createElement('canvas'); canvas.width = 200; // canvas宽度 canvas.height = 200; // canvas高度 const ctx = canvas.getContext('2d') document.querySelector('body').appendChild(canvas) window.requestAnimationFrame(draw) function draw() { ctx.clearRect(0, 0, 200, 200) ctx.save() ctx.translate(100, 100) ctx.strokeStyle = "#f60" ctx.beginPath() ctx.arc(0, 0, 50, 0, Math.PI*2) ctx.closePath() ctx.stroke() ctx.restore() window.requestAnimationFrame(draw) } 复制代码
获取
鼠标在canvas的坐标
let mouseX = 0, mouseY = 0 canvas.addEventListener('mousemove', event => { // e.clientX 鼠标在浏览器页面的位置 // e.target.getBoundingClientRect().x 画布在浏览器页面的位置 mouseX = canvas.ratio * (event.clientX - event.target.getBoundingClientRect().x) mouseY = canvas.ratio * (event.clientY - event.target.getBoundingClientRect().y) }) 复制代码
判断鼠标坐标是否落在目标物体内
相关函数ctx.isPointInPath
// app.js let canvas = document.createElement('canvas'); canvas.width = 200; // canvas宽度 canvas.height = 200; // canvas高度 const ctx = canvas.getContext('2d') document.querySelector('body').appendChild(canvas) let mouseX = 0, mouseY = 0 canvas.addEventListener('mousemove', event => { // e.clientX 鼠标在浏览器页面的位置 // e.target.getBoundingClientRect().x 画布在浏览器页面的位置 mouseX = event.clientX - event.target.getBoundingClientRect().x mouseY = event.clientY - event.target.getBoundingClientRect().y }) window.requestAnimationFrame(draw) function draw() { ctx.clearRect(0, 0, 200, 200) ctx.save() ctx.translate(100, 100) ctx.strokeStyle = "#f60" ctx.beginPath() ctx.arc(0, 0, 50, 0, Math.PI*2) ctx.closePath() ctx.stroke() ctx.restore() const inCircle = ctx.isPointInPath(mouseX, mouseY) console.log(inCircle, mouseX, mouseY); if(inCircle) { ctx.beginPath() ctx.arc(100, 100, 50, 0, Math.PI*2) ctx.fillStyle = "#f80" ctx.fill() ctx.closePath() } window.requestAnimationFrame(draw) } 复制代码
在高清画布上选取失败
可是是这个画布在高清屏上是模糊的, 所以我将对canvas进行缩放
// app.js function createCanvas(w = 200, h = 200) { let ratio = window.devicePixelRatio ?? 1; let canvas = document.createElement('canvas'); canvas.style.width = `${w}px`; // 画板显示宽度 canvas.style.height = `${h}px`; // 画板显示高度 canvas.width = w * ratio; // 实际绘图宽度 canvas.height = h * ratio; // 实际绘图高度 // Q: 对画布进行线性变换真的有必要吗? 就按照300px画不行吗? // A: 有的, 因为别人的设备ratio可能2, 也就是说生成画布宽度会是400px, // 那么你画一个200px的进度条, 在300px画布上是66%, 在400px画布上就是50% canvas.getContext('2d').setTransform(ratio, 0, 0, ratio, 0, 0); canvas.w = w canvas.h = h canvas.ratio = ratio return canvas; } let canvas = createCanvas() // let canvas = document.createElement('canvas'); // canvas.width = 200; // canvas宽度 // canvas.height = 200; // canvas高度 const ctx = canvas.getContext('2d') document.querySelector('body').appendChild(canvas) 复制代码
可这又导致了新问题就是: 物体选取失败了
解决方案是, 对
鼠标在canvas的坐标
也进行缩放
以下为最终代码 复制代码
// app.js function createCanvas(w = 200, h = 200) { let ratio = window.devicePixelRatio ?? 1; let canvas = document.createElement('canvas'); canvas.style.width = `${w}px`; // 画板显示宽度 canvas.style.height = `${h}px`; // 画板显示高度 canvas.width = w * ratio; // 实际绘图宽度 canvas.height = h * ratio; // 实际绘图高度 // Q: 对画布进行线性变换真的有必要吗? 就按照300px画不行吗? // A: 有的, 因为别人的设备ratio可能2, 也就是说生成画布宽度会是400px, // 那么你画一个200px的进度条, 在300px画布上是66%, 在400px画布上就是50% canvas.getContext('2d').setTransform(ratio, 0, 0, ratio, 0, 0); canvas.w = w canvas.h = h canvas.ratio = ratio return canvas; } let canvas = createCanvas() // let canvas = document.createElement('canvas'); // canvas.width = 200; // canvas宽度 // canvas.height = 200; // canvas高度 const ctx = canvas.getContext('2d') document.querySelector('body').appendChild(canvas) let mouseX = 0, mouseY = 0 canvas.addEventListener('mousemove', event => { // e.clientX 鼠标在浏览器页面的位置 // e.target.getBoundingClientRect().x 画布在浏览器页面的位置 mouseX = canvas.ratio * (event.clientX - event.target.getBoundingClientRect().x) mouseY = canvas.ratio * (event.clientY - event.target.getBoundingClientRect().y) }) window.requestAnimationFrame(draw) function draw() { ctx.clearRect(0, 0, 200, 200) ctx.save() ctx.translate(100, 100) ctx.strokeStyle = "#f60" ctx.beginPath() ctx.arc(0, 0, 50, 0, Math.PI*2) ctx.closePath() ctx.stroke() ctx.restore() const inCircle = ctx.isPointInPath(mouseX, mouseY) console.log(inCircle, mouseX, mouseY); if(inCircle) { ctx.beginPath() // ctx.arc(0, 0, 50, 0, Math.PI*2) // ctx.arc(canvas.w/2, canvas.h/2, 50, 0, Math.PI*2) ctx.arc(100, 100, 50, 0, Math.PI*2) ctx.fillStyle = "#f80" ctx.fill() ctx.closePath() } window.requestAnimationFrame(draw) } 复制代码
移动物体
代码写得有点灾难
// app.js function createCanvas(w = 200, h = 200) { let ratio = window.devicePixelRatio ?? 1; let canvas = document.createElement('canvas'); canvas.style.width = `${w}px`; // 画板显示宽度 canvas.style.height = `${h}px`; // 画板显示高度 canvas.width = w * ratio; // 实际绘图宽度 canvas.height = h * ratio; // 实际绘图高度 // Q: 对画布进行线性变换真的有必要吗? 就按照300px画不行吗? // A: 有的, 因为别人的设备ratio可能2, 也就是说生成画布宽度会是400px, // 那么你画一个200px的进度条, 在300px画布上是66%, 在400px画布上就是50% canvas.getContext('2d').setTransform(ratio, 0, 0, ratio, 0, 0); canvas.w = w canvas.h = h canvas.ratio = ratio return canvas; } let canvas = createCanvas(300, 200) const ctx = canvas.getContext('2d') document.querySelector('body').appendChild(canvas) let mouse = { x:0, y:0, scaledX: function(){ return Math.floor(canvas.ratio * this.x) }, scaledY: function(){ return Math.floor(canvas.ratio * this.y) }, } let items = [] let redCircle = new Circle(ctx, { px: 100, py: 100 }) let blueCircle = new Circle(ctx, { px: 220, py: 100, strokeColor: "#28d", fillColor: "#28d", radius: 30 }) items = [redCircle, blueCircle] function draw() { ctx.clearRect(0, 0, canvas.w, canvas.h) redCircle.draw() blueCircle.draw() window.requestAnimationFrame(draw) } window.requestAnimationFrame(draw) canvas.addEventListener('mousemove', event => { // e.clientX 鼠标在浏览器页面的位置 // e.target.getBoundingClientRect().x 画布在浏览器页面的位置 mouse.x = event.clientX - event.target.getBoundingClientRect().x mouse.y = event.clientY - event.target.getBoundingClientRect().y }) canvas.addEventListener('click', event => { const x = mouse.scaledX() const y = mouse.scaledY() items.forEach(item => item.ckeckClicked(x, y)) }) canvas.addEventListener('wheel', event => { const step = event.wheelDelta > 0 ? 0.1 : -0.1 items.forEach(item => { if(item.isSelected) { item.theta += step } }) }) function Circle(ctx, config = {}) { const { px, py, strokeColor, fillColor, radius } = config this.pos = { x: px ?? 0, y: py ?? 0 } this.radius = radius ?? 50 this.theta = 0 this.color = { stroke: strokeColor ?? "#f60", fill: fillColor ?? "#f80" } this.distanceToMouse = { x: 0, y: 0 } this.isSelected = false this.mouseHover = false this.needCkeckClicked = false this.mouseClickX = 0 this.mouseClickY = 0 this.ckeckClicked = function(x, y){ this.needCkeckClicked = true this.mouseClickX = x this.mouseClickY = y this.draw() } this.containPoint = function (x, y) { } this.moveTo = function (x,y) { this.pos.x = x this.pos.y = y } this.draw = function () { ctx.save() ctx.strokeStyle = this.color.stroke ctx.fillStyle = this.color.fill ctx.translate(this.pos.x, this.pos.y) ctx.rotate(this.theta) // ctx.rotate(0) ctx.beginPath() // ctx.arc(0, 0, this.radius, 0, Math.PI * 2) // ctx.fillRect(0, 0, this.radius, this.radius) ctx.moveTo(-this.radius, this.radius); ctx.lineTo(this.radius, this.radius); ctx.lineTo(this.radius, -this.radius); ctx.lineTo(-this.radius, -this.radius); ctx.lineTo(-this.radius, this.radius); ctx.fill() ctx.stroke() if(this.needCkeckClicked) { this.needCkeckClicked = false if(this.isSelected){ this.isSelected = false this.moveTo(this.pos.x, this.pos.y) } else { if(ctx.isPointInPath(this.mouseClickX, this.mouseClickY)) { this.distanceToMouse.x = this.pos.x - mouse.x this.distanceToMouse.y = this.pos.y - mouse.y this.isSelected = true } } } if (ctx.isPointInPath(mouse.scaledX(), mouse.scaledY())) { this.mouseHover = true ctx.globalAlpha = 0.1 ctx.fillStyle = "#fff" ctx.fill() } if(this.isSelected){ ctx.strokeStyle = "#555" ctx.globalAlpha = 0.3 ctx.lineWidth = 2 ctx.stroke() this.moveTo(mouse.x + this.distanceToMouse.x, mouse.y + this.distanceToMouse.y) } ctx.closePath() ctx.restore() } }
伪原创工具 SEO网站优化 https://www.237it.com/
作者:耿直网友郝猪皮
链接:https://juejin.cn/post/7035155955817906190