🐊

cropperjsで写真切り取りのコンポーネントを作る

2023/12/19に公開

cropperjsの概要や使い方ついては、こちらのサイトで紹介されております。
https://zenn.dev/rabee/articles/trimming-cropperjs

Nuxt3での使い方

  • コンポーネントでのインポート
import Cropper from 'cropperjs';

ちなみにoptionsなどの型はCropper.Optionsでインストール可能

  • スタイル設定
    Cropperをインポートするコンポーネントと同じファイルに入れなければ反応しなかった。
    (main.cssに入れたり、style scopedにするとダメ)
<style>
@import "cropperjs/dist/cropper.css";
</style>

使用例

クリックで展開
<script setup lang="ts">
import Cropper from 'cropperjs';

const {imageURL,overwrite,maxSize} = withDefaults(
    defineProps<{imageURL?:string,overwrite?:boolean,maxSize?:number}>(),
    {
      overwrite:false,
      maxSize:1000
    }
)

const temp = ref<string>(imageURL?.startsWith('http')? imageURL:'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==')

const croppedImage=ref<string>('') // base64URL

const croppedImageSize = computed(()=>Math.round(croppedImage.value.length/1.33/1000))

const cropper=ref<Cropper>()

var cropBoxData:Cropper.SetCropBoxDataOptions

const setCroppedImage=()=>{
    if(!cropper.value)return
    const croppedCanvas = cropper.value.getCroppedCanvas();
    croppedImage.value = croppedCanvas.toDataURL('image/png');
}

const options:Cropper.Options={
    aspectRatio: 1 / 1,
    modal:false,
    minCropBoxHeight:100,
    minCropBoxWidth:100,
    cropend:()=>{
        setCroppedImage()
        cropBoxData = cropper.value?.getCropBoxData()
    },
    ready:()=>{
        if(cropBoxData){
            cropper.value?.setCropBoxData(cropBoxData)
        }        
        setCroppedImage()
    }
}

onMounted(()=>{
    if(imageURL?.startsWith('http'))return
    const target = document.getElementById('target') as HTMLImageElement
    cropper.value = new Cropper(target,options);
})

const 画質を下げる=(x:number)=>{ // numberは1から100まで
    const imgElem = new Image();
    imgElem.src = temp.value;

    imgElem.onload = () => {
        const canvas = document.createElement('canvas');
        canvas.width = imgElem.width;
        canvas.height = imgElem.height;

        const ctx = canvas.getContext('2d');
        ctx?.drawImage(imgElem, 0, 0, imgElem.width, imgElem.height);

        canvas.toBlob((blob) => {
            if (!blob) return;

            const reader = new FileReader();
            reader.onloadend = () => {
                cropper.value?.replace(reader.result as string)
            }
            reader.readAsDataURL(blob);
        }, 'image/jpeg', x/100);
    }   
}

const setFile=(files:FileList|null)=>{
    const reader = new FileReader()
    reader.onload = (e)=>{
        const dataURL = e.target?.result as string
        temp.value=dataURL

        cropper.value?.replace(dataURL)
    }

    if(!files||!files.length)return
    const file = files.item(0) as File;
    reader.readAsDataURL(file)
}
    
</script>

<template>
    <div class=" space-y-4">
        <div class="flex items-center justify-center h-72 overflow-hidden">
            <img id="target" :src="temp" class="max-w-full max-h-full">
        </div>

        <div v-if="!imageURL?.startsWith('http')" class="w-full flex ">
            <label>画質</label>
            <input type="range" min="1" value="100" @change="画質を下げる(Number(($event.target as HTMLInputElement).value))"
            class="flex-auto mx-4 "
            >
            <div :class="croppedImageSize>maxSize? 'text-red-500':''">{{ croppedImageSize }}kB</div>
        </div>
            
        <div v-if="croppedImageSize>maxSize" class="text-xs text-red-500">画質を落とすか、選択範囲を狭めて{{maxSize}}kB以内にしてください。</div>        

        <div class=" flex justify-between">
            <label v-if="overwrite || !imageURL?.startsWith('http')">
                <div class="button">選択</div>
                <input type="file" accept="image/jpeg, image/png" class="hidden" @change="setFile(($event.target as HTMLInputElement).files)">
            </label>

            <slot :data="croppedImage" :size="croppedImageSize"></slot>
        </div>
    </div>
</template>

<style>
@import "cropperjs/dist/cropper.css";
</style>

解説

画質を落とす

画質を落とす機能は、imgエレメントとcanvasエレメントを使って実装できる。
canvasで画質を落とす際、toBlobのqualityに数値を設定すれば画質を落とせる。
同じ画像に繰り返し実行しても一度しか画質は落ちないので、一度画質を落としても満足できない場合はもとの画像からqualityにもっと低い数値を入れて画質を落としましょう。

cropper画像の入れ替え

cropper.replace(<base64data>)で、cropperオブジェクトの画像を入れ替えることができる。
その際、トリミングボックスのサイズと位置は初期化されてしまうので、事前にサイズと位置の状態情報は保存しておいて、セットしなおすと良い

cropper optionsのreadyについて

前述のreplaceは処理に少し時間がかかるため、この後にcropperオブジェクトに何か作業させるとcropperがundefined状態となりエラーとなることがある。
setTimeで100ミリ秒くらい置いても良いが、あまり美しくない。
readyに実行内容を書くと、cropperオブジェクトが生成終了した直後に実行させることができる。

Discussion