You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用React Konva实现非裁剪区域变灰的动态裁剪工具

实现React Konva动态裁剪区域的灰化遮罩

核心实现方案:分层遮罩+全局合成模式

最直接高效的方式是通过双层图层+全局合成操作实现:底层图层承载原图与灰化遮罩,通过挖空效果露出裁剪区域;顶层图层放置可交互的裁剪框与Transformer,保证操作不受遮罩干扰。

完整代码示例

import React, { useRef, useState } from 'react';
import { Stage, Layer, Image as KonvaImage, Rect, Transformer } from 'react-konva';
import useImage from 'use-image';

export default function Editor({ imageUrl }) {
  const [image] = useImage(imageUrl);
  const [crop, setCrop] = useState({ x: 100, y: 100, width: 200, height: 150 });
  const rectRef = useRef(null);
  const transformerRef = useRef(null);
  const [selected, setSelected] = useState(false);

  const handleDragEnd = (e) => {
    setCrop(e.target.attrs);
  };

  const handleTransformEnd = () => {
    const node = rectRef.current;
    const scaleX = node.scaleX();
    const scaleY = node.scaleY();

    setCrop({
      x: node.x(),
      y: node.y(),
      width: node.width() * scaleX,
      height: node.height() * scaleY,
    });

    node.scaleX(1);
    node.scaleY(1);
  };

  if (!image) return <div>加载中...</div>;

  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      {/* 底层:原图 + 灰化遮罩(挖空裁剪区域) */}
      <Layer>
        <KonvaImage image={image} />
        {/* 全屏灰色半透明遮罩 */}
        <Rect
          x={0}
          y={0}
          width={window.innerWidth}
          height={window.innerHeight}
          fill="#888"
          opacity={0.6}
          globalCompositeOperation="destination-over"
        />
        {/* 挖空裁剪区域:用destination-out清除遮罩对应位置 */}
        <Rect
          {...crop}
          fill="transparent"
          globalCompositeOperation="destination-out"
        />
      </Layer>

      {/* 顶层:可交互的裁剪框与Transformer */}
      <Layer>
        <Rect
          ref={rectRef}
          {...crop}
          fill="transparent"
          stroke="#fff"
          strokeWidth={2}
          draggable
          onDragEnd={handleDragEnd}
          onTransformEnd={handleTransformEnd}
          onClick={() => setSelected(true)}
          onMouseOver={() => document.body.style.cursor = 'move'}
          onMouseOut={() => document.body.style.cursor = 'default'}
        />
        {selected && (
          <Transformer
            ref={transformerRef}
            node={rectRef.current}
            rotateEnabled={false}
            borderColor="#fff"
            handleFill="#fff"
          />
        )}
      </Layer>
    </Stage>
  );
}

问题解答

  1. 如何实现非裁剪区域灰化、裁剪区域清晰?

    • 采用分层设计:底层图层先渲染原图,再叠加全屏灰色遮罩,最后用destination-out模式的矩形挖空裁剪区域,让原图在该区域透出来;顶层图层单独放置可交互的裁剪框,避免被遮罩层遮挡导致无法操作。
  2. React Konva中更直接的实现方式?

    • 上述分层+全局合成模式就是最优解,无需拼接多个Rect组件。也可尝试Konva的clipFunc自定义裁剪,但对于动态交互场景,全局合成的方式更灵活易维护。
  3. 问题根源是globalCompositeOperation还是图层顺序?

    • 两者都有关:
      • 图层顺序:必须保证遮罩层在原图之上,挖空矩形在遮罩之后绘制;交互层要单独放在顶层,否则会被遮罩拦截点击事件。
      • 合成模式使用:之前效果不佳大概率是因为合成模式应用对象错误,比如把destination-out用在原图上,而非遮罩层,导致原图被挖空而非遮罩。

注意事项

  • 裁剪框需设置fill="transparent",避免遮挡底层原图。
  • Transformer变换结束后要重置裁剪框的缩放比例,防止尺寸计算错误。
  • 可调整遮罩的opacity和fill值,优化视觉反馈效果。

内容的提问来源于stack exchange,提问作者AlePouroullis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 04:20:05