当前位置: 首页 > 知识库问答 >
问题:

javascript - react-konva 如何实现 根据以鼠标为中心缩放图片?

平元明
2023-10-11

原生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

共有1个答案

姚丰羽
2023-10-11

解决这个问题,首先要逆变换求出鼠标点击的位置在变换前的位置,然后将这个位置作为新的变换坐标系原点(原点在线性变换中是不动点,利用这个性质,就可以做出跟随鼠标的效果)。
如果图形已经发生过变换,修改原点会导致“跃变”效果,也就是在鼠标开始动的瞬间图形会跑掉(把setPosition去掉就可以重现这种现象)。因此需要对“跃变”的副作用进行修正,也就是在修改原点的同时,把图像“拽”回原位置。

  const [scale, setScale] = useState(2);  const [offset, setOffset] = useState({    x: 80,    y: 80,  });  const [position, setPosition] = useState({ x: 0, y: 0 });  const wheelCallback = useCallback(    ({      //@ts-ignore      evt: { layerX, layerY, deltaY },    }: Konva.KonvaEventObject<WheelEvent>) => {      if (!shapeRef.current) return;      const currentTransform = shapeRef.current.getTransform();      const currentTransformMatrix = [...currentTransform.getMatrix()];      const { x, y, offsetX, offsetY } = shapeRef.current.attrs;      // 当前的变换坐标原点为 [offsetX, offsetY], 为不变点      // 鼠标点击的位置      const currentViewCursorInLayer = {        x: layerX,        y: layerY,      };      // 当前变换矩阵求逆,以便还原鼠标位置在变换前的真实位置      const currentTransformInvert = currentTransform.copy().invert();      // 逆变换求鼠标位置变换前的位置,所以是 Actual ,该位置将作为新的变换原点      // 该位置实际上是在以 [x, y] 为原点的坐标系中表达的,所以是 InShape      const currentActualCursorInShape = currentTransformInvert.point(        currentViewCursorInLayer      );      // 其实赋值前后是完全等效的,这里为了避免困惑,故重命名      const nextOffset = {        x: currentActualCursorInShape.x,        y: currentActualCursorInShape.y,      };      // 修改原点      setOffset(nextOffset);      // 跟随滚轮缩放      if (deltaY < 0) {        setScale(scale + 0.1);      } else if (scale > 0.1) {        setScale(scale - 0.1);      } else {        setScale(0.1);      }      // 原点位置变更,变更的矢量由旧原点指向新原点,直接移动一下好了      const nextMatrix = [...currentTransformMatrix];      const nextTransform = new Konva.Transform(nextMatrix).translate(        -(nextOffset.x - offsetX),        -(nextOffset.y - offsetY)      );      // 修改原点,会导致位置突变,需要计算突变发生后,鼠标相对于图像的位置会被移动到何处      // 变换只是视觉效果,所以是 View      const nextViewCursorInLayer = nextTransform.point(nextOffset);      // 鼠标位置在两次变化间的移动矢量      const cursorMoveVector = {        x: layerX - nextViewCursorInLayer.x,        y: layerY - nextViewCursorInLayer.y,      };      // 使用前述移动矢量,修正位置      setPosition({        x: x + cursorMoveVector.x,        y: y + cursorMoveVector.y      });    },    [scale, shapeRef]  );      return <Stage width={800} height={800}>        <Layer>          <Group            scale={{ x: scale, y: scale }}            onDragEnd={dragCallback}            onWheel={wheelCallback}            position={position}            offset={offset}            ref={shapeRef}            draggable          >            <Rect              width={160}              height={160}              strokeWidth={1}              stroke="blue"              fill={"red"}            />          </Group>        </Layer>      </Stage>
��这里用的 Rect ,方便测试,Image 差别应该不大。

以上代码在单层未变换 Layer 中测试可用,但没有测试过复杂的复合变换,注意shapeRef要作为Group的属性,而非Image

 类似资料:
  • 预览的时候 发现只有触摸板能实现 缩放 但是客户现场是台式机没有触摸板 尝试了 ctrl + ctrl - 都无效.... 跪求大佬!

  • 我有一个自定义图表,我使用以下代码缩放它: 我注意到当我用鼠标滚轮滚动图表时,我不能缩放鼠标指向的图表。相反,图表会向左或向右缩放。

  • 我尝试了另一篇文章中给出的这个例子,以了解关于相对于鼠标指针的缩放和平移。当所有东西都在网格上时,缩放就像预期的那样工作: 当缩放到左上角图像上的鼠标指针位置时,它将缩放到右上角图像中所见的准确位置。 如果某物被拖出网格,例如,枢轴开始“行为不端”:

  • 本文向大家介绍js实现鼠标拖拽缩放div实例代码,包括了js实现鼠标拖拽缩放div实例代码的使用技巧和注意事项,需要的朋友参考一下 封装为了jq插件,如下 drag.js html文件 以上所述是小编给大家介绍的js实现鼠标拖拽缩放div详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对呐喊教程网站的支持!

  • 但是,当移动鼠标指针时,该功能将被破坏。 为了进一步澄清,如果我在鼠标轮上放大一个缺口,图像就会在正确的位置周围放大。这种行为在我放大鼠标轮的每一个缺口上都继续进行,完全如预期的那样。但是,如果在放大一部分后,我将鼠标移到不同的位置,功能就会中断,我必须完全缩小以改变缩放位置。 预期的行为是为了在缩放过程中鼠标位置的任何变化都能正确地反映在缩放的图像中。 虽然这对于当图像完全缩小时选择的固定鼠标位

  • 本文向大家介绍Jquery实现鼠标移动放大图片功能实例,包括了Jquery实现鼠标移动放大图片功能实例的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了Jquery实现鼠标移动放大图片功能的方法。分享给大家供大家参考。具体分析如下: 做毕业设计,老师看完小样后,嫌弃我购物车里商品图片太大,不美观,让美化个效果。上网查查代码,修改了一个简单版的。 使用的是jquery, 未使用JavaScri