React-横屏签名

160 阅读1分钟
import clsx from 'clsx';
import { useEffect, useRef, useState } from 'react';
import type { CSSProperties } from 'react';
import { Popup } from 'react-vant'; // 不推荐使用移动端特色组件库
import SignatureCanvas from 'react-signature-canvas';

interface Props {
  open: boolean;
  onOpenChange: (value?: boolean) => void;
  onChange: (value?: string) => void;
}
export default function Sign({ open, onOpenChange, onChange }: Props) {
  const signCanvas = useRef<SignatureCanvas>(null);
  const [signVisible, setSignVisible] = useState(false);
  const rotateCanvas = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    setSignVisible(open);
  }, [open]);

  const confirm = () => {
    const canvas = signCanvas.current!.getCanvas();

    // const sign = signCanvas.current!.toDataURL('image/png');
    const sign = rotate(rotateCanvas.current!, canvas, {
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight,
    });
    onChange(sign);
    onOpenChange(false);
  };

  return (
    <Popup
      className={'sign-contracted-modal'}
      visible={signVisible}
      onClose={() => setSignVisible(false)}
      position="top"
      onOpen={() => {
        detectOrient(signCanvas.current!);
      }}
    >
      <canvas ref={rotateCanvas} style={{ display: 'none' }} />
      <div className={'sign-contracted'} id="sign-contracted">
        <div id="sign-content" className={'sign-content'}>
          <SignatureCanvas
            ref={signCanvas}
            penColor="black"
            backgroundColor="#F5F5F5"
            canvasProps={{
              width: 200,
              height: 200,
              className: 'signCanvas',
            }}
          />
          <div className={'sign-bar'}>
            <div className={'actions'}>
              <div
                className={'btn'}
                onClick={() => {
                  signCanvas.current?.clear();
                }}
              >
                清除
              </div>
              <div
                className={clsx(['btn', 'submit'])}
                onClick={() => confirm()}
              >
                确认
              </div>
            </div>
          </div>
        </div>
      </div>
    </Popup>
  );
}

function detectOrient(
  signCanvas?: SignatureCanvas,
  backgroundColor = 'transparent',
) {
  if (!signCanvas) {
    return;
  }
  const width = document.documentElement.clientWidth;
  const height = document.documentElement.clientHeight;
  if (width >= height) {
    return;
  }
  //将整个h5页面翻转
  const $wrapper = document.getElementById('sign-contracted')!;
  if (!$wrapper) {
    return;
  }
  const style: CSSProperties = {
    width: `${height}px`,
    height: `${width}px`,
    transform: `rotate(90deg)`,
    transformOrigin: `${width / 2}px ${width / 2}px`,
    // '-webkit-transform':`rotate(90deg)`,
    // '-webkit-transform-origin':`${width / 2}px ${width / 2}px`,
  };

  $wrapper.style.cssText = js2Css(style);
  //将签名还原翻转,就可以保证在横屏情况下保证画笔的方向跟手势一致,然后再进行高度和宽度的调整。
  const parentElement = document.getElementById('sign-content')!;
  const pw = parentElement.clientWidth;
  const ph = parentElement.clientHeight;
  parentElement.style.cssText = `height: ${ph}px;`;
  const canvasElement = signCanvas.getCanvas();
  canvasElement.height = pw;
  canvasElement.width = ph;

  const canvasStyle: CSSProperties = {
    backgroundColor,
    transform: `rotate(-90deg)`,
    transformOrigin: `${ph / 2}px ${ph / 2}px`,
    // '-webkit-transform':`rotate(-90deg)`,
    // '-webkit-transform-origin':`${ph / 2}px ${ph / 2}px`,
  };
  canvasElement.style.cssText = js2Css(canvasStyle);
}

function js2Css(styleObj: CSSProperties) {
  if (typeof styleObj === 'string') {
    return styleObj;
  }
  return Object.entries(styleObj)
    .filter((item) => item[1] !== undefined && item[1] !== null)
    .map(([property, value]) => `${property}:${value};`)
    .join(' ');
}

function rotate(
  canvas: HTMLCanvasElement,
  image: HTMLCanvasElement,
  size: { width: number; height: number },
) {
  const degree = (270 * Math.PI) / 180;
  const ctx = canvas.getContext('2d')!;
  canvas.width = size.height;
  canvas.height = size.width;
  ctx.rotate(degree);
  ctx.drawImage(image, -size.width, 0);
  const base64 = canvas.toDataURL('image/png');
  return base64;
}

.sign-contracted-modal {
  width: 100%;
  height: 100%;
  .sign-contracted {
    width: 100%;
    height: 100%;
    .sign-content {
      width: 100%;
      height: 100%;
      position: relative;
      .sign-bar {
        width: 100%;
        height: fit-content;
        padding: 0 32px 12px 32px;
        position: absolute;
        left: 0;
        bottom: 0;
        z-index: 10;
        display: flex;
        justify-content: space-between;
        align-items: center;
        .actions {
          display: flex;
          align-items: center;
          gap: 12px;
        }
        .btn {
          width: 108px;
          height: 36px;
          border-radius: 6px;

          font-weight: 500;
          font-size: 16px;
          color: #1c64d7;
          border: 1px solid #1c64d7;
          background: #fff;

          display: flex;
          justify-content: center;
          align-items: center;
          &.submit {
            background: #1c64d7;
            color: rgba(255, 255, 255, 0.95);
          }
        }
      }
    }
  }
}