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

React Konva实现可编辑Text组件遇阻,寻求解决方案

实现React Konva Text组件的可编辑功能

需求说明

  • 按下回车键可换行;
  • 双击Text(支持在Konva Stage内拖动)时,显示文本框接收用户修改默认文本;
  • 双击编辑区域可返回Text组件,修改后的文本需同步到Text内容中;
  • 编辑框需与Text组件处于完全相同的X、Y偏移位置。

已尝试方案

使用react-konva-utils中的Html组件,解决原生HTML元素无法跟随React Konva Text组件移动的兼容问题。

遇到的问题

  • Html组件支持padding和margin,但不支持直接设置top、left属性;
  • 尝试将Text的X值映射为div的margin-top、Y值映射为margin-left,位置匹配效果与预期差距较大。

修正后的代码

import React, { useState, useRef, useEffect, Fragment } from "react";
import { Html } from 'react-konva-utils';
import { Text, Transformer } from "react-konva";

const KText = ({ stage, id, properties, isSelected, onSelect, onChange, setActiveText }) => {
  const shapeRef = useRef();
  const trRef = useRef();
  const [toggleEdit, setToggleEdit] = useState(false);
  const [editText, setEditText] = useState(properties.text);

  useEffect(() => {
    if (isSelected) {
      trRef.current.nodes([shapeRef.current]);
      trRef.current.getLayer().batchDraw();
    }
  }, [isSelected]);

  // 同步编辑框内容到外部状态
  const handleSave = () => {
    setToggleEdit(false);
    setActiveText({ text: editText, id });
  };

  return (
    <>
      {/* 展示模式:Konva Text组件 */}
      {!toggleEdit && (
        <Fragment>
          <Text
            stroke={"black"}
            strokeWidth={1}
            onTap={onSelect}
            onClick={onSelect}
            onDblClick={() => setToggleEdit(true)}
            ref={shapeRef}
            name="text"
            x={properties.x}
            y={properties.y}
            text={properties.text}
            fontFamily={properties.fontFamily}
            fontSize={properties.fontSize}
            fontWeight={properties.fontWeight}
            fillLinearGradientStartPoint={{ x: 0, y: 0 }}
            fillLinearGradientEndPoint={{ x: 100, y: 100 }}
            fillLinearGradientColorStops={[
              0, "rgba(0,0,0,0.7)",
              1, "rgba(255,155,255,0.5)"
            ]}
            fillPriority={"linear-gradient"}
            draggable
            onDragEnd={e => {
              onChange?.({
                ...properties,
                x: e.target.x(),
                y: e.target.y(),
              });
            }}
            onTransformEnd={e => {
              const node = shapeRef.current;
              const scaleX = node.scaleX();
              const scaleY = node.scaleY();
              node.scaleX(1);
              node.scaleY(1);
              onChange?.({
                ...properties,
                x: node.x(),
                y: node.y(),
                fontSize: node.fontSize() * scaleX,
              });
            }}
          />
          {isSelected && <Transformer ref={trRef} />}
        </Fragment>
      )}

      {/* 编辑模式:绑定到Text节点的Html输入框 */}
      {toggleEdit && (
        <Html node={shapeRef.current} groupProps={{ x: 0, y: 0 }}>
          <div 
            style={{
              padding: '4px',
              background: '#fff',
              border: '1px solid #000',
              borderRadius: '4px',
              outline: 'none',
              minWidth: '120px'
            }}
            onDoubleClick={() => setToggleEdit(false)}
          >
            <textarea
              value={editText}
              onChange={(e) => setEditText(e.target.value)}
              rows="4"
              cols="30"
              style={{
                border: 'none',
                resize: 'none',
                background: 'transparent',
                fontSize: `${properties.fontSize}px`,
                fontFamily: properties.fontFamily,
                fontWeight: properties.fontWeight,
                outline: 'none'
              }}
              autoFocus
              onKeyDown={(e) => {
                // 回车键换行,Ctrl+Enter保存(可选)
                if (e.key === 'Enter' && !e.ctrlKey) {
                  e.stopPropagation();
                } else if (e.key === 'Enter' && e.ctrlKey) {
                  handleSave();
                }
              }}
            />
            <button onClick={handleSave} style={{ marginTop: '8px' }}>保存</button>
          </div>
        </Html>
      )}
    </>
  );
};

export default KText;

关键修改说明

  1. 自动位置同步:通过Html组件的node属性绑定到Text的ref,让编辑框自动跟随Text的位置、缩放等变换,无需手动计算偏移量;
  2. 样式匹配:编辑框的字体大小、字体族等样式与Text组件保持一致,提升视觉一致性;
  3. 文本同步逻辑:使用本地状态editText实时保存输入内容,点击保存或Ctrl+Enter时同步到外部状态;
  4. 交互优化:
    • 双击Text切换到编辑模式,双击编辑区域切换回展示模式;
    • textarea支持回车键换行,Ctrl+Enter快速保存;
    • 修复拖拽和缩放后的属性同步逻辑,确保Text组件的位置、字体大小正确更新。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 11:45:26