vinothpandian / react-sketch-canvas

Freehand vector drawing component for React using SVG as canvas 🖌️
https://vinoth.info/react-sketch-canvas
MIT License
456 stars 81 forks source link

Preview for the pointer with strokeWidth #169

Open mung3477 opened 2 months ago

mung3477 commented 2 months ago

Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

First, thank you for implementing this package. It was the only one among canvas drawing packages that supported various devices.

To enhance the user experience, I hope to see where the cursor is positioned and current stroke width. Something like this!

Example

Describe the solution you'd like A clear and concise description of what you want to happen.

To solve this problem, I made a custom hook to implement this. It quite works well, and I think this feature would make this package better!

import { forwardRef, useEffect, useRef, useState } from "react";

interface StrokePreviewProps {
  top: string;
  left: string;
  strokeWidth: number;
}
const StrokePreview = forwardRef<HTMLDivElement, StrokePreviewProps>(
  ({ top, left, strokeWidth }, ref) => (
    <div
      style={{
        top,
        left,
        width: `${strokeWidth}px`,
        height: `${strokeWidth}px`,
        transform: "translate(-50%, -50%)"
      }}
      className="absolute rounded-full bg-[rgba(182,146,246,0.75)]"
      ref={ref}
    />
  )
);

function useStrokePreview() {
  const [top, setTop] = useState<string>("0px");
  const [left, setLeft] = useState<string>("0px");
  const canvasContainerRef = useRef<HTMLDivElement>(null);
  const cursorFollowerRef = useRef<HTMLDivElement>(null);
  const timeoutId = useRef<ReturnType<typeof setTimeout>>();

  useEffect(() => {
    const positionCursorFollower = (e: MouseEvent | PointerEvent) => {
      timeoutId.current = setTimeout(() => {
        if (canvasContainerRef.current) {
          const { offsetHeight, offsetWidth } = canvasContainerRef.current;

          setTop(`${Math.min(offsetHeight, Math.max(0, e.offsetY))}px`);
          setLeft(`${Math.min(offsetWidth, Math.max(0, e.offsetX))}px`);
        }
      }, 1);
    };

    if (canvasContainerRef.current) {
      canvasContainerRef.current.addEventListener(
        "mousemove",
        positionCursorFollower
      );
      canvasContainerRef.current.addEventListener(
        "pointermove",
        positionCursorFollower
      );
    }

    return () => {
      if (timeoutId.current) {
        clearTimeout(timeoutId.current);
      }
      canvasContainerRef?.current?.removeEventListener(
        "mousemove",
        positionCursorFollower
      );
      canvasContainerRef?.current?.removeEventListener(
        "pointermove",
        positionCursorFollower
      );
    };
  }, [!!canvasContainerRef.current]);

  return { top, left, canvasContainerRef, cursorFollowerRef };
}

export default StrokePreview;
export { useStrokePreview };
<div className="relative">
          <StrokePreview
            top={strokeTop}
            left={strokeLeft}
            strokeWidth={strokeWidth}
            ref={cursorFollowerRef}
          />
          <div className="mb-[20px] flex flex-col" ref={canvasContainerRef}>
            <img src={imgSrc} alt="original" />
            <ReactSketchCanvas
              style={{
                position: "absolute",
                top: 0,
                left: 0,
                cursor: "none"
              }}
              canvasColor="transparent"
              strokeColor="rgba(182, 146, 246, 0.75)"
              strokeWidth={strokeWidth}
              eraserWidth={strokeWidth}
              onStroke={onChange}
              ref={skectchCanvasRef}
            />
          </div>
</div>

And the result seems like this. Screenshot 2024-08-27 at 4 42 45 PM

Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered.

Additional context Add any other context or screenshots about the feature request here.

As I am novice in handling interactions with devices like tablets and mobile phones, I think I may have missed sth. Thank you for reading!

xjamundx commented 2 weeks ago

Thanks for sharing this snippet. Was looking for this feature!

mung3477 commented 1 week ago

I appreciate that you made my snippet as a part of the new PR. Thx @xjamundx !