react-cropperのSafari不具合対策

react-cropperとは

cropper.jsをベースとして、画像クロップツールReactコンポーネントです。 https://github.com/roadmanfong/react-cropper

Safariでの不具合

class Demo extends Component {

  _crop(){
    const dataUrl = this.refs.cropper.getCroppedCanvas().toDataURL();
    console.log(dataUrl);
  },

  render() {
    return (
      <Cropper
        ref='cropper'
        crop={this._crop.bind(this)} />
    );
  }
}

上記は基本的な使い方です。

this.refs.cropper.getCroppedCanvas().toDataURL();でクロップ結果をプリントします。

もし画像の色が豊富だと、Safari(PCもスマホも)ではcross-originエラーが出ます。

不具合対策

クロップページで操作完了したあと、直接にthis.refs.cropper.getCroppedCanvas().toDataURL();base64結果を出すじゃなくて、結果のクロップデータ(x,y,width,height)と元画像によってプレビュー用のcanvasを作リます。

ユーザーさんがレビューしたあと保存するとき、プレビュー用のクロップ結果canvasのtoDataURL();を呼び出します。

class CropperResultCanvas extends React.Component<CropperResultCanvasProps, {}> {
  private canvas: HTMLCanvasElement;
  private setCanvasRef = (ref: HTMLCanvasElement) => {
    this.canvas = ref;
  }

  public toDataURL = () => (
    this.canvas.toDataURL()
  )

  componentDidMount() {
    this.drawCanvas();
  }

  private drawCanvas() {
    const { originImageUrl, cropperData } = this.props;
    const canvas = this.canvas;

    const img = new Image();
    img.crossOrigin = "anonymous";
    img.onload = () => {
      const context = canvas.getContext("2d");
      (context as any).imageSmoothingQuality = "medium";
      if (context) {
        const horizontalRatio = canvas.width / cropperData.width;
        const verticalRatio = canvas.height / cropperData.height;
        const ratio = Math.max(horizontalRatio, verticalRatio);

        const centerShiftX = (canvas.width - cropperData.width * ratio) / 2;
        const centerShiftY = (canvas.height - cropperData.height * ratio) / 2;

        context.drawImage(
          img,
          cropperData.x,
          cropperData.y,
          cropperData.width,
          cropperData.height,
          centerShiftX,
          centerShiftY,
          cropperData.width * ratio,
          cropperData.height * ratio,
        );
      }
    };

    img.src = originImageUrl;
  }

    render() {
    const { width, height, className } = this.props;

    return (
      <canvas
        {...{ width, height, className }}
        ref={this.setCanvasRef}
      />
    );
  }
}

img.crossOrigin = "anonymous";は重要です。入れないとcross-originが出ます。

toDataURL()メソッドでbase64結果が出力できます。

まとめ

レビュー用canvasのdrawImageによってクロップ結果を描きます。

不具合回避の一方、出力したbase64結果はライバーリーで直接に出力した結果よりファイルサイズが小さくなる利点もあります。