🔥

konvajsでobject-fitを実現するにはどうするか

2 min read

こんにちは。
最近canvas(konvajs)にハマっているckoshien(シーこうしえん)です。

経緯

「キャップ野球情報局」というWEBサービスを作っているのですが、
選手の写真をフレームで飾って選手カード風味にするということをしています。

ただ、この実装、CSSで

borderBottomLeftRadius:'880px 400px'

画像のふちを丸めているのですが、どうもとてつもなく重いらしく、
度々丸めたところが灰色になって描画が追いつかないという問題がありました。

じゃあcanvasで書こう

canvasを使うためにkonvajsとreact-konvaを使います。
当然のことながら、konvaにはCSSで実装されていたobject-fit(画像を要素に合わせて自動的にリサイズするプロパティ)が使えません。

CSS konvajs
描画 重い 軽い
object-fit ✖️

konvajsではどう実現するか

RectのfillPatternImageで画像を描画します。
ただ、それだけではobject-fitは実現できません。
まず、リサイズ前の幅と高さ、リサイズ後の幅と高さの倍率を計算します。
アスペクト比を崩さないために、倍率の大きい方をそれぞれ幅と高さに乗算します。
それから、画像の中央が要素の中央に来るようにオフセットを計算します。
これでほぼobject-fitが実現できます。

const BackGroundImage:React.FC<{cornerRadius:number[], x:number, y:number, width:number, height:number, image:string, strokeColor?:string}> = ({ image, x, y, width, height, cornerRadius, strokeColor }) => {
    const [imgElement] = useImage(image);
    if(!imgElement){
      return <></>
    }
    let scaleX = 1;
    let scaleY = 1;
    let offsetX = 0;
    let offsetY = 0;
    scaleX = width/imgElement.width;
    scaleY = height/imgElement.height;
    if(scaleY > scaleX){
      offsetX = (imgElement.width * Math.max(scaleX,scaleY) - width)/2;
    }else{
      offsetY = (imgElement.height * Math.max(scaleX,scaleY) - height)/2;
    }
    offsetX, offsetY);
    return <Rect 
            fillPatternImage={imgElement} 
            x={x} y={y} width={width} height={height}
            cornerRadius={cornerRadius}
            stroke={strokeColor}
            strokeWidth={2}
            fillPatternScale={{
              x: Math.max(scaleX,scaleY),
              y: Math.max(scaleX,scaleY)
            }}
            fillPatternOffset={{
              x: offsetX,
              y: offsetY
            }}
          />;
  };

chroma.jsなどでグラデーションを加えたり、Starコンポーネントも使って
以前のノッペリした印象を軽減したのがこちらです。