【发布时间】:2018-11-10 00:07:41
【问题描述】:
我有一个复杂的问题(我做了很多研究,但无法找到我正在寻找的东西),基本上我正在构建一个标签工具,我可以在其中获取一组图像并且我希望能够单击对象的角并创建用户单击的点。
需要注意的几点(我已经做过这些)
- 图像可以是任何方向,我需要旋转它们(从一个方向旋转)
- 图像应开始按比例缩放以适合画布(将比例设置为从图像和画布尺寸“缩小”)
- 用户可以“平移”(基于箭头键的翻译)
- 用户可以放大和缩小图像(使用 shift + 向上/向下箭头进行缩放)
- 用户可以将图像重置为中心(空格键中心,shift +空格键重置缩放初始和重新居中)
我现在遇到的问题是我正在构建点击部分(我在光标位置绘制一个点)。我已经尝试了多种方法将鼠标坐标放在正确的位置(考虑比例、平移和旋转),但我很难把头绕在它周围。希望得到一些关于如何基本上反转旋转、缩放和平移的帮助或指示,我已申请将点放在正确的位置。
为了提供一些真实的背景信息,我制作了一个 Codepen 来展示正在发生的事情。
Codepen to see it live with the arrow keys / clicks on the canvas
const red = '#ff0000';
class App extends React.Component<{}, {}> {
private canvas: HTMLCanvasElement
private image = new Image
private ctx: CanvasRenderingContext2D | null
private data: any
private orientation: number = 270
private moveKeys: {[key: number]: number} = {}
private cw: number
private ch: number
private scaleFactor: number = 1.00
private startX: number
private startY: number
private panX: number
private panY: number
private isShiftPressed: boolean
private defaultScaleFactor: number = 1.00
private imagePoints: number[][] = []
loadImage = (url: string) => {
this.image.onload = () => {
const iw = this.orientation === 0 || this.orientation === 180 ? this.image.width : this.image.height
const ih = this.orientation === 0 || this.orientation === 180 ? this.image.height : this.image.width
const smaller = Math.min(this.canvas.width / iw, this.canvas.height / ih)
this.defaultScaleFactor = smaller
this.scaleFactor = smaller
}
this.image.src = 'https://i.stack.imgur.com/EYvnM.jpg'
}
componentWillUnmount() {
document.removeEventListener('keyup', this.handleKeyUp)
document.removeEventListener('keydown', this.handleKeyDown)
// window.removeEventListener('resize', this.resizeCanvas)
this.canvas.removeEventListener('click', this.handleCanvasClick)
}
componentDidMount() {
this.isShiftPressed = false
document.addEventListener('keyup', this.handleKeyUp)
document.addEventListener('keydown', this.handleKeyDown)
// window.addEventListener('resize', this.resizeCanvas) // dont need for this example
requestAnimationFrame(this.animate)
const elem = document.getElementById('canvasContainer')
if (!elem) return
const rect = elem.getBoundingClientRect()
this.canvas.addEventListener('click', this.handleCanvasClick)
this.canvas.width = rect.width
this.canvas.height = rect.height
this.ctx = this.canvas.getContext('2d')
this.cw = this.canvas.width
this.ch = this.canvas.height
this.startX = -(this.cw / 2)
this.startY = -(this.ch / 2)
this.panX = this.startX
this.panY = this.startY
this.loadImage()
}
handleCanvasClick = (e) => {
let rect = this.canvas.getBoundingClientRect()
let x = e.clientX - rect.left
let y = e.clientY - rect.top
this.imagePoints.push([x, y])
}
animate = () => {
Object.keys(this.moveKeys).forEach( key => {
this.handleMovement(key, this.moveKeys[key])
})
this.drawTranslated()
requestAnimationFrame(this.animate)
}
handleMovement = (key, quantity) => {
const moveUnit = 20
switch (parseInt(key)) {
case 32: // spacebar
this.panX = this.startX
this.panY = this.startY
if (this.isShiftPressed) {
this.scaleFactor = this.defaultScaleFactor
}
break
case 37: // left
if (this.orientation === 90 || this.orientation === 270) {
this.panY -= moveUnit
} else {
this.panX -= moveUnit
}
break
case 38: // up
if (this.isShiftPressed) {
this.scaleFactor *= 1.1
} else {
if (this.orientation === 90 || this.orientation === 270) {
this.panX += moveUnit
} else {
this.panY += moveUnit
}
}
break
case 39: // right
if (this.orientation === 90 || this.orientation === 270) {
this.panY += moveUnit
} else {
this.panX += moveUnit
}
break
case 40: // down
if (this.isShiftPressed) {
this.scaleFactor /= 1.1
} else {
if (this.orientation === 90 || this.orientation === 270) {
this.panX -= moveUnit
} else {
this.panY -= moveUnit
}
}
break
default:
break
}
}
handleKeyUp = (e) => {
if (e.shiftKey || e.keyCode === 16) {
this.isShiftPressed = false
}
delete this.moveKeys[e.keyCode]
}
handleKeyDown = (e) => {
e.preventDefault()
if (e.shiftKey || e.keyCode === 16) {
this.isShiftPressed = true
}
e.keyCode in this.moveKeys ? this.moveKeys[e.keyCode] += 1 : this.moveKeys[e.keyCode] = 1
}
drawTranslated = () => {
if (!this.ctx) return
const ctx = this.ctx
ctx.clearRect(0, 0, this.cw, this.ch)
ctx.save()
ctx.translate(this.cw / 2, this.ch / 2)
ctx.rotate(this.orientation * Math.PI / 180)
ctx.scale(this.scaleFactor, this.scaleFactor)
ctx.translate(this.panX, this.panY)
const transformedWidth = this.canvas.width / 2 - this.image.width / 2
const transformedHeight = this.canvas.height / 2 - this.image.height / 2
ctx.drawImage(
this.image,
transformedWidth,
transformedHeight
)
const pointSize = 10
if (this.imagePoints.length > 0) {
this.imagePoints.forEach( ([x, y]) => {
ctx.fillStyle = red
ctx.beginPath()
// Obviously the x and y here need to be transformed to work with the current scale, rotation and translation. But I'm stuck here!
ctx.arc(x, y, pointSize, 0, Math.PI * 2, true)
ctx.closePath()
ctx.fill()
})
}
ctx.restore()
}
handleResetUserClicks = () => {
this.imagePoints = []
}
render() {
return (
<div id="container">
<div>Use arrow keys to pan the canvas, shift + up / down to zoom, spacebar to reset</div>
<div id="canvasContainer">
<canvas ref={this.assignCameraRef} id="canvasElement" style={{ position: 'absolute' }} ref={this.assignCameraRef} />
</div>
<div>
<button onClick={this.handleResetUserClicks}>Reset Clicks</button>
</div>
</div>
)
}
assignCameraRef = (canvas: HTMLCanvasElement) => this.canvas = canvas
}
请忽略缺少定义的道具和少数硬编码的值(如方向)。我删除了一些代码并将其抽象为更通用,其中一部分意味着将图像 url 硬编码为我在网上找到的虚拟 URL,并为该图像设置一些参数。
【问题讨论】:
标签: javascript reactjs typescript canvas