原生canvas 可以实现 ,但是用的 konva 库, 不知道怎么做了就
需求是 需要想百度地图 高德地图哪种类似的 以所选区域为中心缩放
https://codesandbox.io/s/affectionate-resonance-2f7lhn?file=/...
import { useEffect, useRef, useState } from "react"
import { useSelector } from "react-redux"
import { Stage, Layer, Group, Image } from "react-konva"
import { vec2 } from "gl-matrix"
import styled from "styled-components"
import Konva from "konva"
import useImage from "use-image"
import type { KonvaEventObject } from "konva/lib/Node"
import type { RootState } from "@/stores"
const ScStage = styled(Stage)`
width: 100%;
height: calc(100% - 100px);
overflow: hidden;
`
const StageContainer2 = () => {
const { imgUrl } = useSelector((state: RootState) => state.measure)
const { cursor } = useSelector((state: RootState) => state.cursor)
const { lateral } = useSelector((state: RootState) => state.showPoint)
const { rotate, scaleX, contrast, brightness } = useSelector((state: RootState) => state.transform)
const [scale, setScale] = useState(1)
const [width, setWidth] = useState(1000)
const [height, setHeight] = useState(1000)
const [image] = useImage(imgUrl)
const stageRef = useRef<Konva.Stage | null>(null)
const layerRef = useRef<Konva.Layer | null>(null)
const imageRef = useRef(null)
const [offsetX, setOffsetX] = useState(0)
const [offsetY, setOffsetY] = useState(0)
const max = 4 // 放大最大的比例
const min = 0.5 // 缩小最小的比例
const step = 0.03 // 每次缩放的比例
function onWheel(e: KonvaEventObject<WheelEvent>) {
const x = e.evt.offsetX
const y = e.evt.offsetY
const offsetX = (x - layerRef.current.offsetX()) * layerRef.current.scaleX() / (layerRef.current.scaleX() - step) - (x - layerRef.current.offsetX())
const offsetY = (y - layerRef.current.offsetY()) * layerRef.current.scaleY() / (layerRef.current.scaleY() - step) - (y - layerRef.current.offsetY())
// 这里写的不是很对
if (e.evt.wheelDelta && e.evt.wheelDelta > 0) {
// 放大
if (layerRef.current.scaleX() < max && layerRef.current.scaleY() < max) {
layerRef.current.scaleX(layerRef.current.scaleX() + step)
layerRef.current.scaleY(layerRef.current.scaleY() + step)
layerRef.current.move({ x: -offsetX, y: -offsetY }) // 跟随鼠标偏移位置
}
} else {
// 缩小
if (layerRef.current.scaleX() > min && layerRef.current.scaleY() > min) {
layerRef.current.scaleX(layerRef.current.scaleX() - step)
layerRef.current.scaleY(layerRef.current.scaleY() - step)
layerRef.current.move({ x: offsetX, y: offsetY }) // 跟随鼠标偏移位置
}
}
// const stage = e.target.getStage()
// const mousePos = stage?.getPointerPosition()!
//
// const imagePos = imageRef.current?.position()!
}
useEffect(() => {
const stage = stageRef.current?.getStage()
const stageWrapper = stageRef.current!.attrs.container
setStageSize()
function setStageSize() {
stage?.width(stageWrapper.clientWidth)
stage?.height(stageWrapper.clientHeight)
setWidth(stageWrapper.clientWidth)
setHeight(stageWrapper.clientHeight)
}
}, [stageRef])
return (
<>
<ScStage ref={stageRef} style={{ cursor }} onWheel={onWheel}>
<Layer ref={layerRef} scaleX={scaleX} rotation={rotate} x={width / 2} y={height / 2}>
<Group draggable
scale={{ x: scale, y: scale }}
offset={{ x: image?.width! / 2, y: image?.height! / 2 }}
>
{lateral && (
<Image
ref={imageRef}
image={image}
filters={[Konva.Filters.Brighten, Konva.Filters.Contrast]}
contrast={contrast}
brightness={brightness}
/>
)}
</Group>
</Layer>
</ScStage>
</>
)
}
export default StageContainer2
解决这个问题,首先要逆变换求出鼠标点击的位置在变换前的位置,然后将这个位置作为新的变换坐标系原点(原点在线性变换中是不动点,利用这个性质,就可以做出跟随鼠标的效果)。
如果图形已经发生过变换,修改原点会导致“跃变”效果,也就是在鼠标开始动的瞬间图形会跑掉(把
setPosition
去掉就可以重现这种现象)。因此需要对“跃变”的副作用进行修正,也就是在修改原点的同时,把图像“拽”回原位置。以上代码在单层未变换
Layer
中测试可用,但没有测试过复杂的复合变换,注意shapeRef
要作为Group
的属性,而非Image
。