Search code examples
htmlreactjstextkonva

I tried to implement this HTML Text demo with React Konva, but in vain, because


What do I want to do:

I want to make React Konva Text editable. That is if I double-click on the Text... (I can move it to anywhere in the Konva Stage) ...I can show a text area to get the edits from the user, any changes he/she would like to make to the default text.

Conditions:

  • Enter key (keyboard) should produce a new line.
  • Double click on Text should show this text area on the same offset X and Y of this Text.
  • Double click on div should take back to Text. Now if there was any change done to the text, then it should get reflected on the Text component's label

I tried to implement this HTML Text demo with React Konva, but in vain, because of limitations

The html Konva demo that I want to reproduce in React Konva

Things I did:

  1. Since HTML 5.0 is incompatible if used in conjugation with React Konva components, like Text, Image, RegularPolygon ... etc.
  2. I used { Html } from 'react-konva-utils' so that I could move that content along with the Text component like in the demo in the link above.

Things that I observed:

  1. The HTML can take padding and margin (we can use normal html within tag.), but not top, left.

  2. I did try to put X property of Text in the margin top of the root div and value of Text's Y property in the margin left attribute of the same div, but I had to revert as it was not close to the demo above.

Code:

import React, { useState, useRef, useEffect, Fragment } from "react";
import { Html } from 'react-konva-utils';
import { Text, Transformer } from "react-konva";
/* 
Konva warning: tr.setNode(shape), tr.node(shape) and tr.attachTo(shape) methods are deprecated. Please use tr.nodes(nodesArray) instead. 
*/
const KText = ({ stage, id, properties, isSelected, onSelect, onChange, setActiveText }) => {
  const shapeRef = useRef();
  const trRef = useRef();
  const [toggleEdit, setToggleEdit] = useState(false)
  useEffect(() => {
    if (isSelected) {
      trRef.current.nodes([shapeRef.current]);
      trRef.current.getLayer().batchDraw();
    }
  }, [isSelected]);
  // console.log("KText", `properties: ${JSON.stringify(properties)}`)
  // console.log("KText", ` properties.text: ${properties.text}`)
  const EditTextField = () => {
    var textProps
    const updateText = (data) => {
      textProps = data
      // console.log("EditTextField", `textProps: ${JSON.stringify(textProps)}`)
    }

    // var mAreaPos = areaPosition()

    const areaPosition = () => {
      let stage1 = stage.current.getStage()
      return ({
        x: stage1.container().offsetLeft + properties.x,
        y: stage1.container().offsetTop + properties.y,
      })
    };

    return (
      <Html >
        <div style={{
          margin: "200px", padding: "20px", background: "lavender",
          borderRadius: 20, borderStyle: "solid", borderColor: "green",
          top: areaPosition().x, left: areaPosition().y
        }}
          onDoubleClick={() => setToggleEdit(!toggleEdit)}>
          <label htmlFor="inputText">Please enter some text below:</label><p>
            <textarea onChange={(evt) => (updateText({ text: evt.target.value, id: id }))}
              id="inputText" name="inputText" rows="4" cols="50" placeholder="Please enter here" />
            <br />
            <button type="text" onClick={() => {
              setToggleEdit(!toggleEdit)
              setActiveText(textProps)
            }}>Close</button>
          </p>
        </div>{/*  */}
      </Html >
    )
  }
  const MainText = () => {
    return (
      <>
        <Fragment>
          <Text
            stroke={"black"}
            strokeWidth={1}
            onTap={onSelect}
            onClick={onSelect}
            onDblClick={() => setToggleEdit(!toggleEdit)}
            ref={shapeRef}
            // {...shapeProps}
            name="text"
            x={properties.x}
            y={properties.y}
            text={properties.text}
            fontFamily={properties.fontFamily}//"Serif"
            fontSize={properties.fontSize}//50
            fontWeight={properties.fontWeight} //"bold"
            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({
                  ...shapeProps,
                  x: e.target.x(),
                  y: e.target.y(),
                });*/
            }}
            onTransformEnd={e => {
              // transformer is changing scale
              /*  const node = shapeRef.current;
                const scaleX = node.scaleX();
                const scaleY = node.scaleY();
                node.scaleX(1);
                node.scaleY(1);
                onChange({
                  ...shapeProps,
                  x: node.x(),
                  y: node.y(),
                  width: node.width() * scaleX,
                  height: node.height() * scaleY,
                }); */
            }}
          />
          {isSelected && <Transformer ref={trRef} />}
        </Fragment>
      </>
    )
  }
  const RenderThis = () => {
    let inText = "" + properties.text
    if (inText.trim().length === 0 || toggleEdit) {
      return (
        <EditTextField />
      )
    } else return (
      <MainText />
    )
  }

  // rendering function
  return (
    <RenderThis />
  );
};
export default KText;

Solution

  • Since top and left properties were not working, I simply replaced the following code:

    <div style={{
      margin: "200px", padding: "20px", background: "lavender",
      borderRadius: 20, borderStyle: "solid", borderColor: "green",
      top: areaPosition().x, left: areaPosition().y
            }}
      onDoubleClick={() => setToggleEdit(!toggleEdit)}>
    ...
    </div
    

    with the following (it improved a bit):

    I used marginTop and marginLeft and removed margin as well.

    Secondly, x should go with marginLeft and y with marginTop, so I corrected that as well.

    <div style={{
       padding: "20px", background: "lavender",
       borderRadius: 20, borderStyle: "solid", borderColor: "green",
       marginTop: areaPosition().y, marginLeft: areaPosition().x
       }}
       onDoubleClick={() => setToggleEdit(!toggleEdit)}>
     ...
    </div