Skip to content

Vue

Vue 3 Composition API

vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { HqCropper, type HqCropperInstance } from 'hq-cropper'

const avatar = ref('')
let cropper: HqCropperInstance

onMounted(() => {
    cropper = HqCropper((base64) => {
        avatar.value = base64
    })
})

const openCropper = () => cropper.open()
</script>

<template>
    <div>
        <img v-if="avatar" :src="avatar" alt="Avatar" />
        <button @click="openCropper">Upload Avatar</button>
    </div>
</template>

With Configuration

vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import {
    HqCropper,
    type HqCropperInstance,
    type ApplicationState,
} from 'hq-cropper'

const image = ref('')
const fileName = ref('')
const error = ref<string | null>(null)

let cropper: HqCropperInstance

onMounted(() => {
    cropper = HqCropper(
        (base64, blob, state: ApplicationState) => {
            image.value = base64
            fileName.value = state.fileName
            error.value = null
        },
        {
            outputSize: 256,
            type: 'jpeg',
            compression: 0.8,
            maxFileSize: 5 * 1024 * 1024,
        },
        undefined,
        (message) => {
            error.value = message
        }
    )
})
</script>

<template>
    <div class="profile-upload">
        <div v-if="error" class="error">{{ error }}</div>

        <div v-if="image" class="preview">
            <img :src="image" alt="Profile" />
            <p>{{ fileName }}</p>
        </div>
        <div v-else class="placeholder">No image selected</div>

        <button @click="cropper.open()">
            {{ image ? 'Change Image' : 'Upload Image' }}
        </button>
    </div>
</template>

Composable

typescript
// composables/useCropper.ts
import { ref, onMounted, type Ref } from 'vue'
import {
    HqCropper,
    type HqCropperInstance,
    type ConfigurationOptions,
    type ApplicationState,
} from 'hq-cropper'

interface UseCropperReturn {
    image: Ref<string | null>
    blob: Ref<Blob | null>
    state: Ref<ApplicationState | null>
    open: () => void
    clear: () => void
}

export function useCropper(
    config?: Partial<ConfigurationOptions>,
    onError?: (error: string) => void
): UseCropperReturn {
    const image = ref<string | null>(null)
    const blob = ref<Blob | null>(null)
    const state = ref<ApplicationState | null>(null)

    let cropper: HqCropperInstance

    onMounted(() => {
        cropper = HqCropper(
            (base64, blobData, stateData) => {
                image.value = base64
                blob.value = blobData
                state.value = stateData
            },
            config,
            undefined,
            onError
        )
    })

    const open = () => cropper?.open()

    const clear = () => {
        image.value = null
        blob.value = null
        state.value = null
    }

    return { image, blob, state, open, clear }
}
vue
<script setup lang="ts">
import { useCropper } from '@/composables/useCropper'

const { image, open, clear } = useCropper({ outputSize: 512 }, (error) =>
    alert(error)
)
</script>

<template>
    <div>
        <img v-if="image" :src="image" alt="" />
        <button @click="open">Select Image</button>
        <button v-if="image" @click="clear">Clear</button>
    </div>
</template>

With Form

vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { HqCropper, type HqCropperInstance } from 'hq-cropper'

const name = ref('')
const avatar = ref<Blob | null>(null)
const preview = ref('')

let cropper: HqCropperInstance

onMounted(() => {
    cropper = HqCropper(
        (base64, blob) => {
            preview.value = base64
            avatar.value = blob
        },
        { outputSize: 256 }
    )
})

const submit = async () => {
    const formData = new FormData()
    formData.append('name', name.value)
    if (avatar.value) {
        formData.append('avatar', avatar.value, 'avatar.jpg')
    }

    await fetch('/api/register', {
        method: 'POST',
        body: formData,
    })
}
</script>

<template>
    <form @submit.prevent="submit">
        <div class="avatar-section">
            <img v-if="preview" :src="preview" alt="Avatar" />
            <button type="button" @click="cropper.open()">Upload Avatar</button>
        </div>

        <input v-model="name" placeholder="Name" />
        <button type="submit">Register</button>
    </form>
</template>

Vue 2 Options API

vue
<template>
    <div>
        <img v-if="avatar" :src="avatar" alt="Avatar" />
        <button @click="openCropper">Upload</button>
    </div>
</template>

<script>
import { HqCropper } from 'hq-cropper'

export default {
    data() {
        return {
            avatar: '',
            cropper: null,
        }
    },
    mounted() {
        this.cropper = HqCropper(
            (base64) => {
                this.avatar = base64
            },
            {
                outputSize: 256,
            }
        )
    },
    methods: {
        openCropper() {
            this.cropper.open()
        },
    },
}
</script>

Released under the MIT License.