This paper mainly introduces the function of picture zooming and dragging on Canvas, which is compatible with PC and mobile terminals
Event listeners
PC event monitoring
The first is all kinds of event listening. PC listening to zoom wheel events (mousewheel, wheel)
this.canvasRef.addEventListener('mousedown', this.startMouse.bind(this)) this.canvasRef.addEventListener('mousemove', this.moveMouse.bind(this)) this.canvasRef.addEventListener('mouseup', this.endMouse.bind(this)) this.canvasRef.addEventListener('mousewheel', This. The mouseWheel. Bind (this)) / / listening roller enclosing canvasRef. AddEventListener (' wheel 'this. The mouseWheel. Bind (this)) / / to monitor rollerCopy the code
Mobile event listening
Mobile zooming is listening for multi-finger touches
this.canvasRef.addEventListener('touchstart'.this.startTouch.bind(this))
this.canvasRef.addEventListener('touchmove'.this.moveTouch.bind(this))
this.canvasRef.addEventListener('touchend'.this.endMouse.bind(this))
Copy the code
Image to load
Image loading uses Promise to handle loading and crossOrigin addresses cross-domain.
private loadImage(url: string) {
return new Promise((reject, resolve) = > {
this.img = new Image();
this.img.crossOrigin = 'Anonymous'
this.img.onload = function() {
reject(' ');
}
this.img.onerror = function(error) {
console.error(error, 'error=====')
resolve(error)
}
this.img.src = url
})
}
Copy the code
Mobile location
Calculates the position of the mouse or touch relative to the Canvas container
/** * Handle mouse position *@private
* @param {number} startX
* @param {number} startY
* @returns {IPos}
* @memberof MapCanvas* /
private windowToCanvas(startX: number, startY: number): IPos {
const { left, top, width, height} = this.canvasRef.getBoundingClientRect();
return {
x: startX - left - (width - this.canvasRef.width) / 2.y: startY - top - (height - this.canvasRef.height) / 2}}Copy the code
Moving picture
The PC and mobile end move position logic is similar, according to the current move position minus the last move position.
/** * drag to move *@private
* @param {(React.MouseEvent<HTMLElement> } e
* @memberof MapCanvas* /
private moveMouse(e: React.MouseEvent<HTMLElement> ) {
if(!this.isMove) return false
const { pageX, pageY } = e
this.movePos = this.windowToCanvas(pageX, pageY)
const x = this.movePos.x - this.startPos.x, y = this.movePos.y - this.startPos.y;
this.imgX += x;
this.imgY += y;
this.startPos = {... this.movePos}// Update the latest location
this.drawImage()
}
Copy the code
Draw pictures
Before drawing, you must first clear the previous drawing
/** * draw a picture *@private
* @memberof MapCanvas* /
private drawImage() {
// Clear the previous frame draw
this.ctx.clearRect(0.0.this.canvasRef.width, this.canvasRef.height)
// Draw a picture
this.ctx.drawImage(
this.img,
0.0.this.img.width,
this.img.height,
this.imgX,
this.imgY,
this.img.width * this.imgScale,
this.img.height * this.imgScale
)
}
Copy the code
The zoom
The zoom events are different on PC and mobile
PC zoom
Whether to monitor scroll narrowing or zoom in on PC is judged according to the value of wheelDelta of the rolling event. If the value is greater than 0, it is zoom in; otherwise, it is zoom in.
/** **@private
* @param {(React.WheelEvent<HTMLElement> & { wheelDelta: number })} e
* @memberof MapCanvas* /
private mouseWheel(e: React.WheelEvent<HTMLElement> & { wheelDelta: number } ) {
const { clientX, clientY, wheelDelta } = e
const pos = this.windowToCanvas(clientX, clientY)
// Calculate the position of the image
const newPos = { x: Number(((pos.x - this.imgX)/this.imgScale).toFixed(2)), y: Number(((pos.y - this.imgY)/this.imgScale).toFixed(2))}// Determine whether to zoom in or out
if(wheelDelta > 0) { Enlarge / /
this.imgScale += 0.05
if(this.imgScale >= this.MAX_SCALE) {
this.imgScale = this.MAX_SCALE
}
} else { / / to narrow
this.imgScale -= 0.05
if(this.imgScale <= this.MINIMUM_SCALE) {
this.imgScale = this.MINIMUM_SCALE
}
}
// Calculate the position of the image, according to the current zoom, calculate the new position
this.imgX = (1-this.imgScale)*newPos.x+(pos.x-newPos.x);
this.imgY = (1-this.imgScale)*newPos.y+(pos.y-newPos.y);
this.drawImage(); // Start drawing pictures
}
Copy the code
Mobile scaling
The zoom of the mobile image is judged by the size between the sliding end and the sliding end
/** * Pythagorean theorem, find the straight line distance between two points *@private
* @param {React.Touch} p1
* @param {React.Touch} p2
* @returns {number}
* @memberof MapCanvas* /
private getDistance(p1: React.Touch, p2: React.Touch): number {
const x = p2.pageX - p1.pageX
const y = p2.pageY - p1.pageY
// Find the distance between two points
return Math.sqrt((x * x) + (y * y))
}
Copy the code
Compare the distance before and after sliding to judge the scale;
/** * Drag to zoom on the move side *@private
* @param {React.TouchEvent<HTMLElement>} e
* @memberof MapCanvas* /
private moveTouch(e: React.TouchEvent
) {
if(!this.isMove || ! e.touches)return false
const { clientX, clientY } = e.touches[0]
// If it is a single finger
if(e.touches.length < 2) {
this.movePos = this.windowToCanvas(clientX, clientY)
const x = this.movePos.x - this.startPos.x, y = this.movePos.y - this.startPos.y;
this.imgX += x;
this.imgY += y;
this.startPos = {... this.movePos}// Update the latest location
} else {
const now = e.touches
// Process the position
const pos = this.windowToCanvas(clientX, clientY)
const newPos = {x: Number(((pos.x-this.imgX)/this.imgScale).toFixed(2)),y: Number(((pos.y-this.imgY)/this.imgScale).toFixed(2))};
const curPos = this.getDistance(now[0],now[1]); // The current position
const startPos = this.getDistance(this.touchs[0].this.touchs[1]); // The previous position
// Determine whether the position is zoomed in or out
if(curPos > startPos) { Enlarge / /
this.imgScale += 0.03
if(this.imgScale >= this.MAX_SCALE) {
this.imgScale = this.MAX_SCALE
}
} else {
this.imgScale -= 0.03
if(this.imgScale <= this.MINIMUM_SCALE) {
this.imgScale = this.MINIMUM_SCALE
}
}
// Calculate the position of the image, with the current scale, calculate the new position
this.imgX = (1-this.imgScale)*newPos.x+(pos.x-newPos.x);
this.imgY = (1-this.imgScale)*newPos.y+(pos.y-newPos.y);
this.touchs = now
}
this.drawImage()
}
Copy the code
The last
All the code
interface IPos {
x: number
y: number
}
class MapCanvas {
private canvasRef:HTMLCanvasElement
private ctx: CanvasRenderingContext2D
private img: HTMLImageElement
private startPos: IPos = { x: 0.y: 0 } // Start coordinates
private touchs: React.TouchList // Store multi-finger position
private movePos: IPos // Store the moving coordinate position
private imgX: number = 0 // The image initializes the X position
private imgY: number = 60 // The image initializes the Y position
private isMove: boolean = false // Whether to move
private imgScale: number = 0.5 // Image scale
private MINIMUM_SCALE: number = 0.2 // Minimum zoom
private MAX_SCALE: number = 5 // Maximum zoom
constructor(canvas: HTMLCanvasElement) {
this.canvasRef = canvas
const { width, height} = this.canvasRef.getBoundingClientRect();
this.canvasRef.width = width
this.canvasRef.height = height
this.ctx = canvas.getContext('2d')
this.initCavas();
}
/** * initialize *@memberof MapCanvas* /
async initCavas() {
await this.loadImage('https://img.qlchat.com/qlLive/activity/image/ICLAJAYZ-PZ19-M9WM-1620287616694-OQR1MJFKO67O.jpg')
this.drawImage();
// Event listener on PC
this.canvasRef.addEventListener('mousedown'.this.startMouse.bind(this))
this.canvasRef.addEventListener('mousemove'.this.moveMouse.bind(this))
this.canvasRef.addEventListener('mouseup'.this.endMouse.bind(this))
this.canvasRef.addEventListener('mousewheel'.this.mouseWheel.bind(this)) // Listen to the scroll wheel
this.canvasRef.addEventListener('wheel'.this.mouseWheel.bind(this)) // Listen to the scroll wheel
// Mobile event listener
this.canvasRef.addEventListener('touchstart'.this.startTouch.bind(this))
this.canvasRef.addEventListener('touchmove'.this.moveTouch.bind(this))
this.canvasRef.addEventListener('touchend'.this.endMouse.bind(this))}/** * image loading *@private
* @param {string} url
* @returns
* @memberof MapCanvas* /
private loadImage(url: string) {
return new Promise((reject, resolve) = > {
this.img = new Image();
this.img.crossOrigin = 'Anonymous'
this.img.onload = function() {
reject(' ');
}
this.img.onerror = function(error) {
console.error(error, 'error=====')
resolve(error)
}
this.img.src = url
})
}
/** * draw a picture *@private
* @memberof MapCanvas* /
private drawImage() {
// Clear the previous frame draw
this.ctx.clearRect(0.0.this.canvasRef.width, this.canvasRef.height)
// Draw a picture
this.ctx.drawImage(
this.img,
0.0.this.img.width,
this.img.height,
this.imgX,
this.imgY,
this.img.width * this.imgScale,
this.img.height * this.imgScale
)
}
/** * start dragging *@private
* @param {(React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>)} e
* @memberof MapCanvas* /
private startMouse(e: React.MouseEvent<HTMLElement> ) {
const { pageX, pageY } = e;
this.isMove = true
this.startPos = this.windowToCanvas(pageX, pageY)
}
/** * start touching *@private
* @param {React.TouchEvent<HTMLElement>} e
* @memberof MapCanvas* /
private startTouch(e: React.TouchEvent<HTMLElement>) {
const { touches } = e
this.isMove = true;
// Check if there are multiple fingers
if(touches.length < 2) {
const { clientX, clientY } = touches[0]
this.startPos = this.windowToCanvas(clientX, clientY) // clientX: Position of touch point relative to browser window
} else {
this.touchs = touches
}
}
/** * drag to move *@private
* @param {(React.MouseEvent<HTMLElement> } e
* @memberof MapCanvas* /
private moveMouse(e: React.MouseEvent<HTMLElement> ) {
if(!this.isMove) return false
const { pageX, pageY } = e
this.movePos = this.windowToCanvas(pageX, pageY)
const x = this.movePos.x - this.startPos.x, y = this.movePos.y - this.startPos.y;
this.imgX += x;
this.imgY += y;
this.startPos = {... this.movePos}// Update the latest location
this.drawImage()
}
/** * Drag to zoom on the move side *@private
* @param {React.TouchEvent<HTMLElement>} e
* @memberof MapCanvas* /
private moveTouch(e: React.TouchEvent
) {
if(!this.isMove || ! e.touches)return false
const { clientX, clientY } = e.touches[0]
// If it is a single finger
if(e.touches.length < 2) {
this.movePos = this.windowToCanvas(clientX, clientY)
const x = this.movePos.x - this.startPos.x, y = this.movePos.y - this.startPos.y;
this.imgX += x;
this.imgY += y;
this.startPos = {... this.movePos}// Update the latest location
} else {
const now = e.touches
// Process the position
const pos = this.windowToCanvas(clientX, clientY)
const newPos = {x: Number(((pos.x-this.imgX)/this.imgScale).toFixed(2)),y: Number(((pos.y-this.imgY)/this.imgScale).toFixed(2))};
const curPos = this.getDistance(now[0],now[1]); // The current position
const startPos = this.getDistance(this.touchs[0].this.touchs[1]); // The previous position
// Determine whether the position is zoomed in or out
if(curPos > startPos) { Enlarge / /
this.imgScale += 0.03
if(this.imgScale >= this.MAX_SCALE) {
this.imgScale = this.MAX_SCALE
}
} else {
this.imgScale -= 0.03
if(this.imgScale <= this.MINIMUM_SCALE) {
this.imgScale = this.MINIMUM_SCALE
}
}
// Calculate the position of the image, with the current scale, calculate the new position
this.imgX = (1-this.imgScale)*newPos.x+(pos.x-newPos.x);
this.imgY = (1-this.imgScale)*newPos.y+(pos.y-newPos.y);
this.touchs = now
}
this.drawImage()
}
/** * Drag end *@private
* @param {(React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>)} e
* @memberof MapCanvas* /
private endMouse(e: React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>) {
this.isMove = false
}
/** **@private
* @param {(React.WheelEvent<HTMLElement> & { wheelDelta: number })} e
* @memberof MapCanvas* /
private mouseWheel(e: React.WheelEvent<HTMLElement> & { wheelDelta: number } ) {
const { clientX, clientY, wheelDelta } = e
const pos = this.windowToCanvas(clientX, clientY)
// Calculate the position of the image
const newPos = { x: Number(((pos.x - this.imgX)/this.imgScale).toFixed(2)), y: Number(((pos.y - this.imgY)/this.imgScale).toFixed(2))}// Determine whether to zoom in or out
if(wheelDelta > 0) { Enlarge / /
this.imgScale += 0.05
if(this.imgScale >= this.MAX_SCALE) {
this.imgScale = this.MAX_SCALE
}
} else { / / to narrow
this.imgScale -= 0.05
if(this.imgScale <= this.MINIMUM_SCALE) {
this.imgScale = this.MINIMUM_SCALE
}
}
// Calculate the position of the image, according to the current zoom, calculate the new position
this.imgX = (1-this.imgScale)*newPos.x+(pos.x-newPos.x);
this.imgY = (1-this.imgScale)*newPos.y+(pos.y-newPos.y);
this.drawImage(); // Start drawing pictures
}
/** * Handle mouse position *@private
* @param {number} startX
* @param {number} startY
* @returns {IPos}
* @memberof MapCanvas* /
private windowToCanvas(startX: number, startY: number): IPos {
const { left, top, width, height} = this.canvasRef.getBoundingClientRect();
return {
x: startX - left - (width - this.canvasRef.width) / 2.y: startY - top - (height - this.canvasRef.height) / 2}}/** * Pythagorean theorem, find the straight line distance between two points *@private
* @param {React.Touch} p1
* @param {React.Touch} p2
* @returns {number}
* @memberof MapCanvas* /
private getDistance(p1: React.Touch, p2: React.Touch): number {
const x = p2.pageX - p1.pageX
const y = p2.pageY - p1.pageY
return Math.sqrt((x * x) + (y * y))
}
}
const CanvasMap:React.FC<IP> = ({}: IP) = > {
const canvasRef = React.useRef<HTMLCanvasElement>()
React.useEffect(() = > {
new MapCanvas(canvasRef.current);
}, [])
return (
<div className={ styles['map-box'] }>
<div>
<canvas ref={canvasRef}></canvas>
</div>
</div>)}export default CanvasMap
Copy the code
That’s the end of this article. I hope it helps.
Xiaobian for the first time to write an article writing style is limited, untalented and shallow, the article if there is wrong, hope to inform.