|
@@ -0,0 +1,128 @@
|
|
|
+<template>
|
|
|
+ <div :class="$attrs.class" :style="getWrapperStyle">
|
|
|
+ <img
|
|
|
+ v-show="isReady"
|
|
|
+ ref="imgElRef"
|
|
|
+ :src="src"
|
|
|
+ :alt="alt"
|
|
|
+ :crossorigin="crossorigin"
|
|
|
+ :style="getImageStyle"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+<script lang="ts">
|
|
|
+ import type { CSSProperties } from 'vue';
|
|
|
+
|
|
|
+ import { defineComponent, onMounted, ref, unref, computed, PropType } from 'vue';
|
|
|
+
|
|
|
+ import Cropper from 'cropperjs';
|
|
|
+ import 'cropperjs/dist/cropper.css';
|
|
|
+
|
|
|
+ type Options = Cropper.Options;
|
|
|
+
|
|
|
+ const defaultOptions: Cropper.Options = {
|
|
|
+ aspectRatio: 16 / 9,
|
|
|
+ zoomable: true,
|
|
|
+ zoomOnTouch: true,
|
|
|
+ zoomOnWheel: true,
|
|
|
+ cropBoxMovable: true,
|
|
|
+ cropBoxResizable: true,
|
|
|
+ toggleDragModeOnDblclick: true,
|
|
|
+ autoCrop: true,
|
|
|
+ background: true,
|
|
|
+ highlight: true,
|
|
|
+ center: true,
|
|
|
+ responsive: true,
|
|
|
+ restore: true,
|
|
|
+ checkCrossOrigin: true,
|
|
|
+ checkOrientation: true,
|
|
|
+ scalable: true,
|
|
|
+ modal: true,
|
|
|
+ guides: true,
|
|
|
+ movable: true,
|
|
|
+ rotatable: true,
|
|
|
+ };
|
|
|
+ export default defineComponent({
|
|
|
+ props: {
|
|
|
+ src: {
|
|
|
+ type: String,
|
|
|
+ required: true,
|
|
|
+ },
|
|
|
+ alt: {
|
|
|
+ type: String,
|
|
|
+ },
|
|
|
+ height: {
|
|
|
+ type: [String, Number],
|
|
|
+ default: '360px',
|
|
|
+ },
|
|
|
+ crossorigin: {
|
|
|
+ type: String,
|
|
|
+ default: undefined,
|
|
|
+ },
|
|
|
+ imageStyle: {
|
|
|
+ type: Object as PropType<CSSProperties>,
|
|
|
+ default: {},
|
|
|
+ },
|
|
|
+ options: {
|
|
|
+ type: Object as PropType<Options>,
|
|
|
+ default: {},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ setup(props, ctx) {
|
|
|
+ const imgElRef = ref<ElRef<HTMLImageElement>>(null);
|
|
|
+ const cropper: any = ref<Nullable<Cropper>>(null);
|
|
|
+
|
|
|
+ const isReady = ref(false);
|
|
|
+
|
|
|
+ const getImageStyle = computed(
|
|
|
+ (): CSSProperties => {
|
|
|
+ return {
|
|
|
+ height: props.height,
|
|
|
+ maxWidth: '100%',
|
|
|
+ ...props.imageStyle,
|
|
|
+ };
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ const getWrapperStyle = computed(
|
|
|
+ (): CSSProperties => {
|
|
|
+ const { height } = props;
|
|
|
+ return { height: `${height}`.replace(/px/, '') + 'px' };
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ async function init() {
|
|
|
+ const imgEl = unref(imgElRef);
|
|
|
+ if (!imgEl) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ cropper.value = new Cropper(imgEl, {
|
|
|
+ ...defaultOptions,
|
|
|
+ ready: () => {
|
|
|
+ isReady.value = true;
|
|
|
+ },
|
|
|
+ ...props.options,
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // event: return base64 and width and height information after cropping
|
|
|
+ const croppered = (): void => {
|
|
|
+ let imgInfo = cropper.value.getData();
|
|
|
+ cropper.value.getCroppedCanvas().toBlob(blob => {
|
|
|
+ let fileReader: FileReader = new FileReader()
|
|
|
+ fileReader.onloadend = (e: any) => {
|
|
|
+ ctx.emit("cropperedInfo", {
|
|
|
+ imgBase64: e.target.result,
|
|
|
+ imgInfo
|
|
|
+ })
|
|
|
+ }
|
|
|
+ fileReader.readAsDataURL(blob)
|
|
|
+ }, 'image/jpeg')
|
|
|
+ }
|
|
|
+
|
|
|
+ onMounted(init);
|
|
|
+
|
|
|
+ return { imgElRef, getWrapperStyle, getImageStyle, isReady, croppered };
|
|
|
+ },
|
|
|
+ });
|
|
|
+</script>
|