├── .npmrc ├── next ├── src │ ├── vite-env.d.ts │ ├── assets │ │ └── logo.png │ ├── main.ts │ ├── code.vue │ └── App.vue ├── .gitignore ├── dist │ ├── favicon.ico │ ├── index.css │ ├── vue-cropper.umd.js │ └── vue-cropper.es.js ├── public │ └── favicon.ico ├── shims-vue.d.ts ├── tsconfig.json ├── lib │ ├── typings │ │ └── index.d.ts │ ├── index.ts │ └── exif-js-min.js ├── tsconfig.node.json ├── index.html ├── vite.config.ts ├── tsconfig.app.json ├── package.json ├── README.md └── test │ └── index.html ├── .gitignore ├── scripts └── deploy.sh ├── src ├── index.js └── exif-js-min.js ├── dist └── index.js.map ├── LICENSE ├── webpack.config.js ├── package.json ├── CHANGELOG.md ├── README.md ├── english.md └── docs ├── vue2.html └── vue3.html /.npmrc: -------------------------------------------------------------------------------- 1 | registry = https://registry.npmjs.org/ -------------------------------------------------------------------------------- /next/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /next/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist-ssr 4 | *.local 5 | yarn.lock 6 | package-lock.json -------------------------------------------------------------------------------- /next/dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyxiao001/vue-cropper/HEAD/next/dist/favicon.ico -------------------------------------------------------------------------------- /next/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyxiao001/vue-cropper/HEAD/next/public/favicon.ico -------------------------------------------------------------------------------- /next/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyxiao001/vue-cropper/HEAD/next/src/assets/logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | .DS_Store 4 | .idea/ 5 | .vscode/ 6 | yarn.lock 7 | package-lock.json -------------------------------------------------------------------------------- /next/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { Component, DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, Component> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.app.json" 6 | }, 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /next/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | // import VueCropper from '../lib'; 4 | // import VueCropper from 'vue-cropper'; 5 | // import { VueCropper } from 'vue-cropper'; 6 | 7 | // import 'vue-cropper/dist/index.css' 8 | 9 | const app = createApp(App) 10 | // app.use(VueCropper) 11 | app.mount('#app') -------------------------------------------------------------------------------- /next/lib/typings/index.d.ts: -------------------------------------------------------------------------------- 1 | import VueCropper from '../vue-cropper.vue' 2 | import { globalCropper } from '../index' 3 | import type { App } from 'vue'; 4 | export interface vueCropperGlobal { 5 | version: string, 6 | install: (app: App) => void, 7 | VueCropper: typeof VueCropper 8 | } 9 | 10 | export { 11 | VueCropper 12 | } 13 | 14 | export default globalCropper -------------------------------------------------------------------------------- /next/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 | "skipLibCheck": true, 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "noEmit": true 11 | }, 12 | "include": ["vite.config.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /next/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | vue-cropper@next 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # abort on errors 4 | set -e 5 | 6 | # build 7 | npm run build:docs 8 | 9 | # navigate into the build output directory 10 | cd docs 11 | 12 | # if you are deploying to a custom domain 13 | # echo 'www.example.com' > CNAME 14 | 15 | git init 16 | git add -A 17 | git commit -m 'docs: 🎉 deploy' 18 | 19 | git push -f git@github.com:xyxiao001/vue-cropper.git master:gh-pages 20 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import VueCropper from './vue-cropper' 2 | 3 | const install = function(Vue) { 4 | Vue.component('VueCropper', VueCropper); 5 | } 6 | 7 | /* istanbul ignore if */ 8 | if (typeof window !== 'undefined' && window.Vue) { 9 | install(window.Vue); 10 | } 11 | 12 | export { VueCropper } 13 | 14 | export default { 15 | version: '0.6.5', 16 | install, 17 | VueCropper, 18 | vueCropper: VueCropper 19 | } 20 | -------------------------------------------------------------------------------- /next/lib/index.ts: -------------------------------------------------------------------------------- 1 | import VueCropper from './vue-cropper.vue' 2 | import type { vueCropperGlobal } from './typings' 3 | import type { App } from 'vue'; 4 | 5 | const install = function(app: App) { 6 | app.component('VueCropper', VueCropper) 7 | } 8 | 9 | export const globalCropper: vueCropperGlobal = { 10 | version: '1.1.4', 11 | install, 12 | VueCropper, 13 | } 14 | 15 | export { VueCropper } 16 | 17 | export default globalCropper 18 | -------------------------------------------------------------------------------- /dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","mappings":"AAAA","sources":["webpack://vue-cropper/webpack/universalModuleDefinition"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine(\"vue-cropper\", [], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"vue-cropper\"] = factory();\n\telse\n\t\troot[\"vue-cropper\"] = factory();\n})(self, () => {\nreturn "],"names":[],"sourceRoot":""} -------------------------------------------------------------------------------- /next/vite.config.ts: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | import vue from '@vitejs/plugin-vue' 3 | import { defineConfig } from 'vite' 4 | import { resolve } from 'path' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | server: { 9 | port: 3000, 10 | }, 11 | build: { 12 | lib: { 13 | entry: resolve(__dirname, './lib/index.ts'), 14 | name: 'vue-cropper', 15 | fileName: (format) => `vue-cropper.${format}.js` 16 | }, 17 | // css不要拆分 18 | cssCodeSplit: true, 19 | rollupOptions: { 20 | // 确保外部化处理那些你不想打包进库的依赖 21 | external: ['vue'], 22 | output: { 23 | // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量 24 | globals: { 25 | vue: 'Vue' 26 | } 27 | } 28 | }, 29 | }, 30 | plugins: [vue()] 31 | }) -------------------------------------------------------------------------------- /next/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 5 | "target": "ES2020", 6 | "useDefineForClassFields": true, 7 | "module": "ESNext", 8 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 9 | "skipLibCheck": true, 10 | 11 | /* Bundler mode */ 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "moduleDetection": "force", 17 | "noEmit": true, 18 | "jsx": "preserve", 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true 25 | }, 26 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "lib/**/*.vue", "lib/**/*.ts", "shims-vue.d.ts"] 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 goodboy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-cropper", 3 | "version": "1.1.4", 4 | "description": "A simple Vue picture clipping plugin", 5 | "keywords": [ 6 | "vue", 7 | "vue3", 8 | "cropper", 9 | "vue-cropper", 10 | "vue-component", 11 | "vue-cropper-component" 12 | ], 13 | "main": "./dist/vue-cropper.es.js", 14 | "typings": "./lib/typings/index.d.ts", 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/xyxiao001/vue-cropper.git" 18 | }, 19 | "author": "xyxiao001", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/xyxiao001/vue-cropper/issues" 23 | }, 24 | "homepage": "https://github.com/xyxiao001/vue-cropper#readme", 25 | "scripts": { 26 | "dev": "vite", 27 | "build": "vue-tsc && vite build", 28 | "serve": "vite preview" 29 | }, 30 | "devDependencies": { 31 | "@types/babel__core": "^7.1.19", 32 | "@types/node": "^18.15.0", 33 | "@vitejs/plugin-vue": "^4.0.0", 34 | "vite": "^4.1.0", 35 | "vue": "^3.3.4", 36 | "vue-cropper": "^1.0.3", 37 | "typescript": "^5.5.3", 38 | "vue-tsc": "^2.0.26" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const VueLoaderPlugin = require('vue-loader/lib/plugin') 3 | 4 | module.exports = { 5 | devtool: 'cheap-module-source-map', 6 | entry: './src/index.js', 7 | output: { 8 | path: path.resolve(__dirname, './dist/'), 9 | filename: 'index.js', 10 | library: 'vue-cropper', 11 | libraryTarget: 'umd', 12 | umdNamedDefine: true 13 | }, 14 | resolve: { 15 | extensions: ['.js', '.vue'], 16 | alias: { 17 | 'vue$': 'vue/dist/vue.common.js' 18 | } 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.js$/, 24 | exclude: /(node_modules|bower_components)/, 25 | use: { 26 | loader: 'babel-loader', 27 | options: { 28 | presets: ['@babel/preset-env'], 29 | plugins: ['@babel/transform-runtime'] 30 | } 31 | } 32 | }, 33 | { 34 | test: /\.vue$/, 35 | loader: 'vue-loader' 36 | }, 37 | { 38 | test: /\.css$/, 39 | use: [ 40 | 'style-loader', 41 | 'css-loader' 42 | ] 43 | } 44 | ] 45 | }, 46 | mode: "production", 47 | plugins: [ 48 | // 请确保引入这个插件! 49 | new VueLoaderPlugin() 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /next/src/code.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 24 | 25 | 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-cropper", 3 | "version": "0.6.5", 4 | "description": "A simple Vue picture clipping plugin", 5 | "keywords": [ 6 | "vue", 7 | "cropper", 8 | "vue-cropper", 9 | "vue-component", 10 | "vue-cropper-component" 11 | ], 12 | "main": "./dist/index.js", 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/xyxiao001/vue-cropper.git" 16 | }, 17 | "author": "goodboy", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/xyxiao001/vue-cropper/issues" 21 | }, 22 | "homepage": "https://github.com/xyxiao001/vue-cropper#readme", 23 | "scripts": { 24 | "build": "rm -rf ./dist && webpack --config webpack.config.js" 25 | }, 26 | "devDependencies": { 27 | "@babel/core": "^7.1.2", 28 | "@babel/plugin-transform-runtime": "^7.1.0", 29 | "@babel/preset-env": "^7.1.0", 30 | "babel-loader": "^8.0.0-beta.0", 31 | "babel-plugin-transform-runtime": "^6.23.0", 32 | "babel-polyfill": "^6.26.0", 33 | "babel-runtime": "^6.26.0", 34 | "css-loader": "^6.8.1", 35 | "style-loader": "^3.3.3", 36 | "vue": "^2.5.17", 37 | "vue-loader": "^15.4.2", 38 | "vue-template-compiler": "^2.5.17", 39 | "webpack": "^5.76.0", 40 | "webpack-cli": "^5.0.1", 41 | "sass": "^1.37.5" 42 | }, 43 | "browserify": { 44 | "transform": [ 45 | "vueify" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /next/dist/index.css: -------------------------------------------------------------------------------- 1 | .vue-cropper[data-v-a742df44]{position:relative;width:100%;height:100%;box-sizing:border-box;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;direction:ltr;touch-action:none;text-align:left;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-box[data-v-a742df44],.cropper-box-canvas[data-v-a742df44],.cropper-drag-box[data-v-a742df44],.cropper-crop-box[data-v-a742df44],.cropper-face[data-v-a742df44]{position:absolute;top:0;right:0;bottom:0;left:0;-webkit-user-select:none;user-select:none}.cropper-box-canvas img[data-v-a742df44]{position:relative;text-align:left;-webkit-user-select:none;user-select:none;transform:none;max-width:none;max-height:none}.cropper-box[data-v-a742df44]{overflow:hidden}.cropper-move[data-v-a742df44]{cursor:move}.cropper-crop[data-v-a742df44]{cursor:crosshair}.cropper-modal[data-v-a742df44]{background:rgba(0,0,0,.5)}.cropper-view-box[data-v-a742df44]{display:block;overflow:hidden;width:100%;height:100%;outline:1px solid #39f;outline-color:#3399ffbf;-webkit-user-select:none;user-select:none}.cropper-view-box img[data-v-a742df44]{-webkit-user-select:none;user-select:none;text-align:left;max-width:none;max-height:none}.cropper-face[data-v-a742df44]{top:0;left:0;background-color:#fff;opacity:.1}.crop-info[data-v-a742df44]{position:absolute;left:0;min-width:65px;text-align:center;color:#fff;line-height:20px;background-color:#000c;font-size:12px}.crop-line[data-v-a742df44]{position:absolute;display:block;width:100%;height:100%;opacity:.1}.line-w[data-v-a742df44]{top:-3px;left:0;height:5px;cursor:n-resize}.line-a[data-v-a742df44]{top:0;left:-3px;width:5px;cursor:w-resize}.line-s[data-v-a742df44]{bottom:-3px;left:0;height:5px;cursor:s-resize}.line-d[data-v-a742df44]{top:0;right:-3px;width:5px;cursor:e-resize}.crop-point[data-v-a742df44]{position:absolute;width:8px;height:8px;opacity:.75;background-color:#39f;border-radius:100%}.point1[data-v-a742df44]{top:-4px;left:-4px;cursor:nw-resize}.point2[data-v-a742df44]{top:-5px;left:50%;margin-left:-3px;cursor:n-resize}.point3[data-v-a742df44]{top:-4px;right:-4px;cursor:ne-resize}.point4[data-v-a742df44]{top:50%;left:-4px;margin-top:-3px;cursor:w-resize}.point5[data-v-a742df44]{top:50%;right:-4px;margin-top:-3px;cursor:e-resize}.point6[data-v-a742df44]{bottom:-5px;left:-4px;cursor:sw-resize}.point7[data-v-a742df44]{bottom:-5px;left:50%;margin-left:-3px;cursor:s-resize}.point8[data-v-a742df44]{bottom:-5px;right:-4px;cursor:se-resize}@media screen and (max-width: 500px){.crop-point[data-v-a742df44]{position:absolute;width:20px;height:20px;opacity:.45;background-color:#39f;border-radius:100%}.point1[data-v-a742df44]{top:-10px;left:-10px}.point2[data-v-a742df44],.point4[data-v-a742df44],.point5[data-v-a742df44],.point7[data-v-a742df44]{display:none}.point3[data-v-a742df44]{top:-10px;right:-10px}.point4[data-v-a742df44]{top:0;left:0}.point6[data-v-a742df44]{bottom:-10px;left:-10px}.point8[data-v-a742df44]{bottom:-10px;right:-10px}} 2 | -------------------------------------------------------------------------------- /src/exif-js-min.js: -------------------------------------------------------------------------------- 1 | const Exif = {}; 2 | 3 | Exif.getData = (img) => new Promise((reslove, reject) => { 4 | let obj = {}; 5 | getImageData(img).then(data => { 6 | obj.arrayBuffer = data; 7 | try { 8 | obj.orientation = getOrientation(data); 9 | } catch { 10 | obj.orientation = -1; 11 | } 12 | reslove(obj) 13 | }).catch(error => { 14 | reject(error) 15 | }) 16 | }) 17 | 18 | // 这里的获取exif要将图片转ArrayBuffer对象,这里假设获取了图片的baes64 19 | // 步骤一 20 | // base64转ArrayBuffer对象 21 | function getImageData(img) { 22 | let data = null; 23 | return new Promise((reslove, reject) => { 24 | if (img.src) { 25 | if (/^data\:/i.test(img.src)) { // Data URI 26 | data = base64ToArrayBuffer(img.src); 27 | reslove(data) 28 | } else if (/^blob\:/i.test(img.src)) { // Object URL 29 | var fileReader = new FileReader(); 30 | fileReader.onload = function (e) { 31 | data = e.target.result; 32 | reslove(data) 33 | }; 34 | objectURLToBlob(img.src, function (blob) { 35 | fileReader.readAsArrayBuffer(blob); 36 | }); 37 | } else { 38 | var http = new XMLHttpRequest(); 39 | http.onload = function () { 40 | if (this.status == 200 || this.status === 0) { 41 | data = http.response 42 | reslove(data) 43 | } else { 44 | throw "Could not load image"; 45 | } 46 | http = null; 47 | }; 48 | http.open("GET", img.src, true); 49 | http.responseType = "arraybuffer"; 50 | http.send(null); 51 | } 52 | } else { 53 | reject('img error') 54 | } 55 | }) 56 | } 57 | 58 | function objectURLToBlob(url, callback) { 59 | var http = new XMLHttpRequest(); 60 | http.open("GET", url, true); 61 | http.responseType = "blob"; 62 | http.onload = function (e) { 63 | if (this.status == 200 || this.status === 0) { 64 | callback(this.response); 65 | } 66 | }; 67 | http.send(); 68 | } 69 | 70 | 71 | 72 | function base64ToArrayBuffer(base64, contentType) { 73 | contentType = contentType || base64.match(/^data\:([^\;]+)\;base64,/mi)[1] || ''; // e.g. 'data:image/jpeg;base64,...' => 'image/jpeg' 74 | base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, ''); 75 | var binary = atob(base64); 76 | // byte length of Uint16Array should be a multiple of 2 77 | var len = binary.length % 2 == 0 ? binary.length : binary.length + 1; 78 | var buffer = new ArrayBuffer(len); 79 | var view = new Uint16Array(buffer); 80 | for (var i = 0; i < len; i++) { 81 | view[i] = binary.charCodeAt(i); 82 | } 83 | return buffer; 84 | } 85 | 86 | // 步骤二,Unicode码转字符串 87 | // ArrayBuffer对象 Unicode码转字符串 88 | function getStringFromCharCode(dataView, start, length) { 89 | var str = ''; 90 | var i; 91 | for (i = start, length += start; i < length; i++) { 92 | str += String.fromCharCode(dataView.getUint8(i)); 93 | } 94 | return str; 95 | } 96 | 97 | // 步骤三,获取jpg图片的exif的角度(在ios体现最明显) 98 | function getOrientation(arrayBuffer) { 99 | var dataView = new DataView(arrayBuffer); 100 | var length = dataView.byteLength; 101 | var orientation; 102 | var exifIDCode; 103 | var tiffOffset; 104 | var firstIFDOffset; 105 | var littleEndian; 106 | var endianness; 107 | var app1Start; 108 | var ifdStart; 109 | var offset; 110 | var i; 111 | // Only handle JPEG image (start by 0xFFD8) 112 | if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) { 113 | offset = 2; 114 | while (offset < length) { 115 | if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) { 116 | app1Start = offset; 117 | break; 118 | } 119 | offset++; 120 | } 121 | } 122 | if (app1Start) { 123 | exifIDCode = app1Start + 4; 124 | tiffOffset = app1Start + 10; 125 | if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') { 126 | endianness = dataView.getUint16(tiffOffset); 127 | littleEndian = endianness === 0x4949; 128 | 129 | if (littleEndian || endianness === 0x4D4D /* bigEndian */) { 130 | if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) { 131 | firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian); 132 | 133 | if (firstIFDOffset >= 0x00000008) { 134 | ifdStart = tiffOffset + firstIFDOffset; 135 | } 136 | } 137 | } 138 | } 139 | } 140 | if (ifdStart) { 141 | length = dataView.getUint16(ifdStart, littleEndian); 142 | 143 | for (i = 0; i < length; i++) { 144 | offset = ifdStart + i * 12 + 2; 145 | if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) { 146 | 147 | // 8 is the offset of the current tag's value 148 | offset += 8; 149 | 150 | // Get the original orientation value 151 | orientation = dataView.getUint16(offset, littleEndian); 152 | 153 | // Override the orientation with its default value for Safari (#120) 154 | // if (IS_SAFARI_OR_UIWEBVIEW) { 155 | // dataView.setUint16(offset, 1, littleEndian); 156 | // } 157 | break; 158 | } 159 | } 160 | } 161 | return orientation; 162 | } 163 | 164 | 165 | 166 | export default Exif -------------------------------------------------------------------------------- /next/lib/exif-js-min.js: -------------------------------------------------------------------------------- 1 | const Exif = {}; 2 | 3 | Exif.getData = (img) => new Promise((reslove, reject) => { 4 | let obj = {}; 5 | getImageData(img).then(data => { 6 | obj.arrayBuffer = data; 7 | try { 8 | obj.orientation = getOrientation(data); 9 | } catch { 10 | obj.orientation = -1; 11 | } 12 | reslove(obj) 13 | }).catch(error => { 14 | reject(error) 15 | }) 16 | }) 17 | 18 | // 这里的获取exif要将图片转ArrayBuffer对象,这里假设获取了图片的baes64 19 | // 步骤一 20 | // base64转ArrayBuffer对象 21 | function getImageData(img) { 22 | let data = null; 23 | return new Promise((reslove, reject) => { 24 | if (img.src) { 25 | if (/^data\:/i.test(img.src)) { // Data URI 26 | data = base64ToArrayBuffer(img.src); 27 | reslove(data) 28 | } else if (/^blob\:/i.test(img.src)) { // Object URL 29 | var fileReader = new FileReader(); 30 | fileReader.onload = function (e) { 31 | data = e.target.result; 32 | reslove(data) 33 | }; 34 | objectURLToBlob(img.src, function (blob) { 35 | fileReader.readAsArrayBuffer(blob); 36 | }); 37 | } else { 38 | var http = new XMLHttpRequest(); 39 | http.onload = function () { 40 | if (this.status == 200 || this.status === 0) { 41 | data = http.response 42 | reslove(data) 43 | } else { 44 | throw "Could not load image"; 45 | } 46 | http = null; 47 | }; 48 | http.open("GET", img.src, true); 49 | http.responseType = "arraybuffer"; 50 | http.send(null); 51 | } 52 | } else { 53 | reject('img error') 54 | } 55 | }) 56 | } 57 | 58 | function objectURLToBlob(url, callback) { 59 | var http = new XMLHttpRequest(); 60 | http.open("GET", url, true); 61 | http.responseType = "blob"; 62 | http.onload = function (e) { 63 | if (this.status == 200 || this.status === 0) { 64 | callback(this.response); 65 | } 66 | }; 67 | http.send(); 68 | } 69 | 70 | 71 | 72 | function base64ToArrayBuffer(base64, contentType) { 73 | contentType = contentType || base64.match(/^data\:([^\;]+)\;base64,/mi)[1] || ''; // e.g. 'data:image/jpeg;base64,...' => 'image/jpeg' 74 | base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, ''); 75 | var binary = atob(base64); 76 | // byte length of Uint16Array should be a multiple of 2 77 | var len = binary.length % 2 == 0 ? binary.length : binary.length + 1; 78 | var buffer = new ArrayBuffer(len); 79 | var view = new Uint16Array(buffer); 80 | for (var i = 0; i < len; i++) { 81 | view[i] = binary.charCodeAt(i); 82 | } 83 | return buffer; 84 | } 85 | 86 | // 步骤二,Unicode码转字符串 87 | // ArrayBuffer对象 Unicode码转字符串 88 | function getStringFromCharCode(dataView, start, length) { 89 | var str = ''; 90 | var i; 91 | for (i = start, length += start; i < length; i++) { 92 | str += String.fromCharCode(dataView.getUint8(i)); 93 | } 94 | return str; 95 | } 96 | 97 | // 步骤三,获取jpg图片的exif的角度(在ios体现最明显) 98 | function getOrientation(arrayBuffer) { 99 | var dataView = new DataView(arrayBuffer); 100 | var length = dataView.byteLength; 101 | var orientation; 102 | var exifIDCode; 103 | var tiffOffset; 104 | var firstIFDOffset; 105 | var littleEndian; 106 | var endianness; 107 | var app1Start; 108 | var ifdStart; 109 | var offset; 110 | var i; 111 | // Only handle JPEG image (start by 0xFFD8) 112 | if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) { 113 | offset = 2; 114 | while (offset < length) { 115 | if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) { 116 | app1Start = offset; 117 | break; 118 | } 119 | offset++; 120 | } 121 | } 122 | if (app1Start) { 123 | exifIDCode = app1Start + 4; 124 | tiffOffset = app1Start + 10; 125 | if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') { 126 | endianness = dataView.getUint16(tiffOffset); 127 | littleEndian = endianness === 0x4949; 128 | 129 | if (littleEndian || endianness === 0x4D4D /* bigEndian */) { 130 | if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) { 131 | firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian); 132 | 133 | if (firstIFDOffset >= 0x00000008) { 134 | ifdStart = tiffOffset + firstIFDOffset; 135 | } 136 | } 137 | } 138 | } 139 | } 140 | if (ifdStart) { 141 | length = dataView.getUint16(ifdStart, littleEndian); 142 | 143 | for (i = 0; i < length; i++) { 144 | offset = ifdStart + i * 12 + 2; 145 | if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) { 146 | 147 | // 8 is the offset of the current tag's value 148 | offset += 8; 149 | 150 | // Get the original orientation value 151 | orientation = dataView.getUint16(offset, littleEndian); 152 | 153 | // Override the orientation with its default value for Safari (#120) 154 | // if (IS_SAFARI_OR_UIWEBVIEW) { 155 | // dataView.setUint16(offset, 1, littleEndian); 156 | // } 157 | break; 158 | } 159 | } 160 | } 161 | return orientation; 162 | } 163 | 164 | 165 | 166 | export default Exif -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 更新日志 2 | ## vue3.x组件更新日志 3 | ### v1.1.4 4 | - 修复ts编译问题 5 | 6 | 7 | ### v1.1.3 8 | - 修复长图缩放问题 9 | - 修复full场景 fillColor不生效问题 10 | - 修复build any type类型问题 11 | 12 | ### v1.1.2 13 | centerBox 缩放贴边问题修复 with LAQKing 14 | 15 | ### v1.1.1 16 | 修复部分 base64 图片长度导致展示问题 17 | 18 | 19 | ### v1.1.0 20 | - 修复 exif 读取图片出错的情况 21 | - 新增 @imgLoadError方法返回对应错误 22 | ### v1.0.9 23 | - 新增 fillCover, 导出图片背景颜色 24 | ### v1.0.8 25 | - 更新特定情况固定比例问题 26 | ### v1.0.7 27 | - 移除 node 版本和 npm 要求 28 | 29 | ### v1.0.6 30 | - 添加最小截图框限制 31 | - 修复 ts 类型问题 32 | - 编译替换为 vite4.x 33 | ### v1.0.5 34 | - vue3版本生命周期修复 unmounted 35 | ### v1.0.4 36 | - 升级最新版本 vue 依赖和 vite 版本问题,解决 ts 类型引入问题 37 | ### v1.0.3 38 | - 升级最新版本 vue 依赖和 vite 版本问题 39 | ### v1.0.2 40 | - 提供 ts .d.ts 类型声明文件 41 | - 修复 vue3 全局使用问题 42 | ### v1.0.1 43 | - 修复固定角度的部分问题 44 | ### v1.0.1 45 | - 修复依赖问题 46 | 47 | ### v1.0.0 48 | - 支持 `vue3` 版本 49 | 50 | ## vue2.x组件更新日志 51 | ### v0.6.5 52 | - 修复长图缩放问题 53 | - 修复full场景 fillColor不生效问题 54 | - 修复build any type类型问题 55 | 56 | ### v0.6.4 57 | 修复部分 base64 图片长度导致展示问题 58 | ### v0.6.3 59 | - 修复 exif 读取图片出错的情况 60 | - 新增 @imgLoadError方法返回对应错误 61 | ### v0.6.2 62 | - 新增 fillCover, 导出图片背景颜色 63 | ### v0.6.1 64 | - 修复部分问题 65 | ### v0.6.0 66 | - 移除 node 版本和 npm 要求 67 | ### v0.59 68 | - 添加最小截图框限制 69 | ### v0.58 70 | - 更新 sass依赖 71 | ### v0.57 72 | - 更新文档 73 | ### v0.56 74 | - 修复绑定事件判断出错的问题 75 | - 修复组件移除没有解绑滚动事件的问题 76 | 77 | ### v0.55 78 | - 修复 ios 版本小于 13.4没有处理图片旋转的 bug 79 | 80 | ### v0.54 81 | - 去除 log 信息 82 | - 修复 pc safari低版本问题 83 | 84 | ### v0.53 85 | - 因为 chrome81 内核版本和 ios13.5 版本修复了图片旋转的 bug 86 | - 插件在新版浏览器默认不处理旋转了,低版本浏览器自动处理 87 | - https://www.chromestatus.com/feature/6313474512650240 88 | 89 | ### v0.52 90 | - 撤回最小弹框属性, 存在弹框拖拽坐标判断的 bug 91 | 92 | ### v0.51 93 | - 更新裁剪框最小属性,限制最小区域,可传1以上的数字和字符串,限制长宽都是这么大 也可以传数组 [90,90] 94 | - `limitMinSize: [Number, Array, String]` 95 | 96 | ### v0.50 97 | - 支持图片清空 98 | - 修复 ie11 ie10 不能使用问题 99 | - 修复 `URL.createObjectURL` 创建后没有销毁的 bug 100 | - 添加截图框修改触发事件 101 | ```js 102 | this.$emit('change-crop-size', { 103 | width: this.cropW, 104 | height: this.cropH 105 | }) 106 | ``` 107 | 108 | 109 | ### v0.49 110 | - 修复滚轮默认事件问题 111 | - 修复html静态文件引入事件触发问题 112 | 113 | ### v0.48 114 | - 修复mode 属性 contain 和cover的显示bug问题 115 | - 修复ios 下面base64图片跨域显示问题 116 | 117 | ### v0.47 118 | - 修复第一次不触发预览的问题 119 | - 新增加图片渲染 mode 功能 120 | 121 | ### v0.46 122 | - 修复图片旋转 bug 123 | - 修复显示的一些 bug 124 | 125 | ### v0.45 126 | - 添加倍数使用 enlarge 127 | - 可以输出裁剪框等比例图片 128 | - 感谢来自于 [hzsrc](https://github.com/hzsrc) 的贡献 129 | - 添加预览框各种比例, 和修复图片截图小数问题 130 | 131 | ### v0.44 132 | - 修复引入方式的问题 133 | - 组件内使用 134 | ```js 135 | import { VueCropper } from vue-cropper 136 | components: { 137 | VueCropper, 138 | } 139 | ``` 140 | 141 | - main.js里面使用 142 | ```js 143 | import VueCropper from vue-cropper 144 | Vue.use(vueCropper) 145 | ``` 146 | 147 | - CDN 方式使用 148 | ```js 149 | 150 | Vue.use(window['vue-cropper']) 151 | ``` 152 | ### v0.43 153 | - 剥离 `exif` 的依赖库, 添加 `exfi-min.js` 减小代码体积 45.9k => 37k 154 | - `build` 升级 `webpack4` 升级 155 | - 添加 `vue install` 方法 156 | - `npm`: Vue.use(VueCropper) 157 | - `web`: Vue.use(window['vue-cropper']) 158 | 159 | ### v0.42 160 | - 修复截图框因为去除小数点的引起的问题 161 | 162 | ### v0.41 163 | - 修复截图框边界问题 164 | 165 | ### v0.40 166 | - 修复 orientation 的处理方式 167 | - 感谢 [Felipe Mengatto](https://github.com/felipemengatto) 的贡献 168 | 169 | ### v0.39 170 | - 修复orientation值不同带来的问题 171 | - 感谢 [Felipe Mengatto](https://github.com/felipemengatto) 的贡献 172 | 173 | ### v0.38 174 | 修改坐标反馈问题 175 | 176 | ### v0.37 177 | - 修复 `centerBox` 的截图超出 1px 问题 178 | - 添加截图 图片移动触发事件 179 | 180 | ### v0.36 181 | - 修复旋转自动生成截图框的错误 182 | - 修改 autocrop 可以动态生成截图框 183 | 184 | ### v0.35 185 | - 修复其他图片没有压缩的问题 186 | 187 | ### v0.34 188 | - 提供移动端崩溃的解决方案 189 | - 修改 `maxImgSize` 为 2000 190 | 191 | ### v0.33 192 | - 提供移动端崩溃的解决方案 193 | - `maxImgSize` 限制图片最大宽度和高度 默认为 2000px 194 | 195 | ### v0.32 196 | - 新增截图框信息展示 197 | - `infoTrue` 198 | - `true` 为展示真实输出图片宽高 199 | - `false` 展示看到的截图框宽高 200 | 201 | ### v0.30 202 | 203 | - 新增获取图片坐标函数 `this.$refs.cropper.getImgAxis()` 204 | - 新增获取截图框坐标函数 `this.$refs.cropper.getCropAxis()` 205 | - 新增对高清设备的兼容 `high` 206 | - 新增截图框限制在图片以内的功能 `centerbox` 207 | - 新增自动生成截图框函数 `this.$refs.cropper.goAutoCrop` 208 | 209 | 210 | ### v0.29 211 | - 新增图片加载的回调 `imgLoad` 返回结果 success, error 212 | 213 | ### v0.28 214 | - 修复截图框固定 截图框会影响原图移动 缩放 215 | 216 | ### v0.27 217 | - 鼠标缩放问题优化 218 | - `img` `max-width` 样式优化 219 | - 新增属性 220 | - `canMove` 是否可以移动图片 默认为是 221 | - `canMoveBox` 是否可以移动截图框 默认为是 222 | - `original` 是否按图片原始比例渲染 默认为否 223 | 224 | 225 | ### v0.26 226 | - 修复火狐浏览器 227 | - 鼠标缩放问题 228 | 229 | ### v0.25 230 | - 修复图片有可能不展示 231 | 232 | ### v0.24 233 | - 修复ios拍照旋转 截图问题 234 | - 添加自动修复图片 235 | - 截图预览代码变更, 修改默认上传图片为 `blob` 预览 236 | ```js 237 | realTime (data) { 238 | this.previews = data 239 | } 240 | ``` 241 | ``` html 242 |
244 |
245 | 246 |
247 |
248 | ``` 249 | 250 | 251 | ### v0.23 252 | - 小优化 253 | 254 | ### v0.22 255 | - 新增修改图片大小函数 256 | - 通过` this.$refs.cropper.changeScale` 调用 257 | 258 | ### v0.21 259 | -新增固定截图框大小fiexdBox(注: 最好搭配自动生成截图框使用) 260 | 261 | ### v0.20 262 | - 新增输出原图比例截图 props 名 full 263 | - 修复缩放图片过大灵敏度问题 264 | 265 | ### v0.19 266 | - 新增图片旋转 267 | - 修复mac滚轮过度灵敏 268 | ``` js 269 | this.$refs.cropper.rotateRight() // 向右边旋转 90 度 270 | this.$refs.cropper.rotateLeft() // 向左边旋转 90 度 271 | ``` 272 | 273 | ### v0.18 274 | - 修复默认生成截图框超过容器错误 275 | 276 | ### v0.17 277 | - 修复blob数据获取错误 278 | 279 | ### v0.15 280 | - 添加手机端手势缩放 281 | ``` 282 | canScale: true 283 | ``` 284 | 285 | ### v0.13 286 | - 添加预览 287 | @realTime="realTime" 288 | ```js 289 | // Real time preview function 290 | realTime (data) { 291 | this.previews = data 292 | } 293 | ``` 294 | ``` html 295 |
297 |
298 | 299 |
300 |
301 | ``` 302 | -------------------------------------------------------------------------------- /next/README.md: -------------------------------------------------------------------------------- 1 | # vue-cropper 2 | 一个优雅的图片裁剪插件 3 | 4 | [ [查看演示 Demo](https://github.xyxiao.cn/vue-cropper/docs/vue3.html) ] 5 | [ [README_english](../english.md) ] 6 | [ [更新日志](../CHANGELOG.md) ] 7 | 8 | 9 | 10 | ## 一、安装使用 11 | 12 | 13 | ### 1. 安装 14 | 15 | ```bash 16 | # npm 安装 17 | npm install vue-cropper 18 | ``` 19 | ```bash 20 | # yarn 安装 21 | yarn add vue-cropper 22 | ``` 23 | 24 | 25 | 如果你没有使用 `npm` 26 | 27 | [在线例子vue-cropper + vue.2x](https://codepen.io/xyxiao001/pen/wxwKGz) 28 | 29 | [在线例子vue-cropper@next + vue.3x](https://codepen.io/xyxiao001/pen/yLooYKg) 30 | 31 | 服务器渲染 `nuxt` 解决方案 设置为 `ssr: false` 32 | ```js 33 | module.exports = { 34 | ... 35 | build: { 36 | vendor: [ 37 | 'vue-cropper 38 | ... 39 | plugins: [ 40 | { src: '~/plugins/vue-cropper', ssr: false } 41 | ] 42 | } 43 | } 44 | ``` 45 | 46 | 47 | ### 2. 引入 Vue Cropper 48 | `Vue 3` 组件内引入 49 | ```bash 50 | npm install vue-cropper@next 51 | import 'vue-cropper/dist/index.css' 52 | import { VueCropper } from "vue-cropper"; 53 | ``` 54 | 55 | `Vue3` 全局引入 56 | ```js 57 | import VueCropper from 'vue-cropper'; 58 | import 'vue-cropper/dist/index.css' 59 | 60 | const app = createApp(App) 61 | app.use(VueCropper) 62 | app.mount('#app') 63 | ``` 64 | 65 | `Vue3 CDN` 方式引入 66 | ```html 67 | 68 | ``` 69 | 70 | ```js 71 | 72 | 73 | const app = Vue.createApp({...}); 74 | app.component('vue-cropper', window['vue-cropper'].VueCropper); 75 | ``` 76 | 77 | 78 | `Vue2` 组件内引入 79 | ```js 80 | import { VueCropper } from 'vue-cropper' 81 | components: { 82 | VueCropper 83 | } 84 | ``` 85 | 86 | `Vue2` 全局引入 87 | ```js 88 | import VueCropper from 'vue-cropper' 89 | Vue.use(VueCropper) 90 | ``` 91 | 92 | 93 | `Vue2 CDN` 方式引入 94 | ```html 95 | 96 | ``` 97 | ```js 98 | Vue.use(window['vue-cropper'].default) 99 | ``` 100 | 101 | 102 | `nuxt` 引入方式 103 | ```js 104 | if(process.browser) { 105 | vueCropper = require('vue-cropper') 106 | Vue.use(vueCropper.default) 107 | } 108 | ``` 109 | 110 | ### 3. 代码中使用 111 | 112 | > **重要!** 需要关掉本地的 mock 服务, 不然图片转化会报错 113 | > **重要!** 需要使用外层容器包裹并设置宽高 114 | 115 | ```html 116 | 122 | ``` 123 | 124 | 125 | ## 二、文档 126 | 127 | ### 1. props 128 | 129 | > 目前还不知道什么原因项目里面开启 `mock` 会导致 file 报错,建议使用时关掉 `mock` 130 | 131 | 132 | 名称 | 功能 | 默认值 | 可选值 133 | --- | --- | --- | --- 134 | img | 裁剪图片的地址 | 空 | `url 地址`, `base64`, `blob` 135 | outputSize | 裁剪生成图片的质量 | 1 | 0.1 ~ 1 136 | img | 裁剪图片的地址 | 空 | `url 地址`, `base64`, `blob` 137 | outputSize | 裁剪生成图片的质量 | `1` | 0.1 ~ 1 138 | outputType | 裁剪生成图片的格式 | jpg (jpg 需要传入jpeg) | `jpeg`, `png`, `webp` 139 | info | 裁剪框的大小信息 | `true` | `true`, `false` 140 | canScale | 图片是否允许滚轮缩放 | `true` | `true`, `false` 141 | autoCrop | 是否默认生成截图框 | `false` | `true`, `false` 142 | autoCropWidth | 默认生成截图框宽度 | 容器的 80% | 0 ~ max 143 | autoCropHeight | 默认生成截图框高度 | 容器的 80% | 0 ~ max 144 | fixed | 是否开启截图框宽高固定比例 | `true` | `true`, `false` 145 | fixedNumber | 截图框的宽高比例 | `[1, 1]` | `[ 宽度 , 高度 ]` 146 | full | 是否输出原图比例的截图 | `false` | `true`, `false` 147 | fixedBox | 固定截图框大小 | 不允许改变 | `false` | `true`, `false` 148 | canMove | 上传图片是否可以移动 | `true` | `true`, `false` 149 | canMoveBox | 截图框能否拖动 | `true` | `true`, `false` 150 | original | 上传图片按照原始比例渲染 | `false` | `true`, `false` 151 | centerBox | 截图框是否被限制在图片里面 | `false` | `true`, `false` 152 | high | 是否按照设备的dpr 输出等比例图片 | `true` | `true`, `false` 153 | infoTrue | true 为展示真实输出图片宽高 `false` 展示看到的截图框宽高 | false | `true`, `false` 154 | maxImgSize | 限制图片最大宽度和高度 | `2000` | 0 ~ max 155 | enlarge | 图片根据截图框输出比例倍数 | `1` | 0 ~ max(建议不要太大不然会卡死的呢) 156 | mode | 图片默认渲染方式 | `contain` | `contain` , `cover`, `100px`, `100%` auto 157 | 158 | ### 2. 可用回调方法 159 | 160 | - `@realTime` 实时预览事件 161 | - `@imgMoving` 图片移动回调函数 162 | - `@cropMoving` 截图框移动回调函数 163 | - `@imgLoad` 图片加载的回调, 返回结果 `success`, `error` 164 | 165 | #### @realTime 实时预览事件 166 | ```js 167 | realTime(data) { 168 | var previews = data 169 | var h = 0.5 170 | var w = 0.2 171 | 172 | this.previewStyle1 = { 173 | width: previews.w + "px", 174 | height: previews.h + "px", 175 | overflow: "hidden", 176 | margin: "0", 177 | zoom: h 178 | } 179 | 180 | this.previewStyle2 = { 181 | width: previews.w + "px", 182 | height: previews.h + "px", 183 | overflow: "hidden", 184 | margin: "0", 185 | zoom: w 186 | } 187 | 188 | // 固定为 100 宽度 189 | this.previewStyle3 = { 190 | width: previews.w + "px", 191 | height: previews.h + "px", 192 | overflow: "hidden", 193 | margin: "0", 194 | zoom: 100 / preview.w 195 | } 196 | 197 | // 固定为 100 高度 198 | this.previewStyle4 = { 199 | width: previews.w + "px", 200 | height: previews.h + "px", 201 | overflow: "hidden", 202 | margin: "0", 203 | zoom: 100 / preview.h 204 | } 205 | this.previews = data 206 | } 207 | ``` 208 | 209 | ```html 210 |
212 |
213 | 214 |
215 |
216 |

中等大小

217 |
218 |
219 | 220 |
221 |
222 | 223 |

迷你大小

224 |
225 |
226 | 227 |
228 |
229 | ``` 230 | 231 | 232 | #### @imgMoving 图片移动回调函数 233 | 234 | 返回的参数内容 235 | ```js 236 | { 237 | moving: true, // moving 是否在移动 238 | axis: { 239 | x1: 1, // 左上角 240 | x2: 1,// 右上角 241 | y1: 1,// 左下角 242 | y2: 1 // 右下角 243 | } 244 | } 245 | ``` 246 | 247 | #### @cropMoving 截图框移动回调函数 248 | 返回的参数内容 249 | ```js 250 | { 251 | moving: true, // moving 是否在移动 252 | axis: { 253 | x1: 1, // 左上角 254 | x2: 1,// 右上角 255 | y1: 1,// 左下角 256 | y2: 1 // 右下角 257 | } 258 | } 259 | ``` 260 | 261 | 262 | ### 2. 内置方法 和 属性 263 | 通过 `this.$refs.cropper` 调用 264 | 265 | **属性** 266 | 267 | 属性 | 说明 268 | --- | --- 269 | this.$refs.cropper.cropW | 截图框宽度 270 | this.$refs.cropper.cropH | 截图框高度 271 | 272 | 273 | **方法** 274 | 275 | 方法 | 说明 276 | --- | --- 277 | this.$refs.cropper.startCrop() | 开始截图 278 | this.$refs.cropper.stopCrop() | 停止截图 279 | this.$refs.cropper.clearCrop() | 清除截图 280 | this.$refs.cropper.changeScale() | 修改图片大小 正数为变大 负数变小 281 | this.$refs.cropper.getImgAxis() | 获取图片基于容器的坐标点 282 | this.$refs.cropper.getCropAxis() | 获取截图框基于容器的坐标点 283 | this.$refs.cropper.goAutoCrop | 自动生成截图框函数 284 | this.$refs.cropper.rotateRight() | 向右边旋转90度 285 | this.$refs.cropper.rotateLeft() | 向左边旋转90度 286 | 287 | **获取截图内容** 288 | 289 | 获取截图的 base64 数据 290 | 291 | ```js 292 | this.$refs.cropper.getCropData(data => { 293 | // do something 294 | console.log(data) 295 | }) 296 | ``` 297 | 298 | 获取截图的 blob 数据 299 | ```js 300 | this.$refs.cropper.getCropBlob(data => { 301 | // do something 302 | console.log(data) 303 | }) 304 | ``` 305 | 306 | 307 | 308 | 309 | 310 | ## 三、相关文章参考 311 | - [shn_ui - vue-cropper ](https://shnhz.github.io/shn-ui/#/component/vue-cropper) - 野宁新之助 312 | - [vue全家桶开发管理后台—裁切图片](https://blog.csdn.net/qq_30632003/article/details/79639346) - 麻球科技-菅双鹏 313 | - [Vue-cropper 图片裁剪的基本原理](https://www.cnblogs.com/tugenhua0707/p/8859291.html) - 龙恩0707 314 | - [关于昵称和头像的总结(模仿微信)](https://zhuanlan.zhihu.com/p/45746753) - 秋晨光 315 | - [vue-cropper-h5](https://github.com/2277419213/vue-cropper-h5) - 居里栈栈 316 | 317 | ## 四、交流 318 | 有什么意见,或者 bug,或者想一起开发 `vue-cropper`, 或者想一起开发其他插件 319 | ![群号 857471950](https://user-images.githubusercontent.com/15681693/134663362-a6940a73-4692-4cc0-985f-109579057014.JPG) 320 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-cropper 2 | 一个优雅的图片裁剪插件 3 | 4 | [ [查看演示 Demo](https://github.xyxiao.cn/vue-cropper/docs/vue2.html) ] 5 | [ [README_english](./english.md) ] 6 | [ [更新日志](./CHANGELOG.md) ] 7 | 8 | 9 | 10 | ## 一、安装使用 11 | 12 | 13 | ### 1. 安装 14 | 15 | ```bash 16 | # npm 安装 17 | npm install vue-cropper 18 | ``` 19 | ```bash 20 | # yarn 安装 21 | yarn add vue-cropper 22 | ``` 23 | 24 | 25 | 如果你没有使用 `npm` 26 | 27 | [在线例子vue-cropper + vue.2x](https://codepen.io/xyxiao001/pen/wxwKGz) 28 | 29 | [在线例子vue-cropper@next + vue.3x](https://codepen.io/xyxiao001/pen/yLooYKg) 30 | 31 | 服务器渲染 `nuxt` 解决方案 设置为 `ssr: false` 32 | ```js 33 | module.exports = { 34 | ... 35 | build: { 36 | vendor: [ 37 | 'vue-cropper 38 | ... 39 | plugins: [ 40 | { src: '~/plugins/vue-cropper', ssr: false } 41 | ] 42 | } 43 | } 44 | ``` 45 | 46 | 47 | ### 2. 引入 Vue Cropper 48 | `Vue 3` 组件内引入 49 | ```bash 50 | npm install vue-cropper@next 51 | import 'vue-cropper/dist/index.css' 52 | import { VueCropper } from "vue-cropper"; 53 | ``` 54 | 55 | `Vue3` 全局引入 56 | ```js 57 | import VueCropper from 'vue-cropper'; 58 | import 'vue-cropper/dist/index.css' 59 | 60 | const app = createApp(App) 61 | app.use(VueCropper) 62 | app.mount('#app') 63 | ``` 64 | 65 | `Vue3 CDN` 方式引入 66 | ```html 67 | 68 | ``` 69 | 70 | ```js 71 | 72 | 73 | const app = Vue.createApp({...}); 74 | app.component('vue-cropper', window['vue-cropper'].VueCropper); 75 | ``` 76 | 77 | 78 | `Vue2` 组件内引入 79 | ```js 80 | import { VueCropper } from 'vue-cropper' 81 | components: { 82 | VueCropper 83 | } 84 | ``` 85 | 86 | `Vue2` 全局引入 87 | ```js 88 | import VueCropper from 'vue-cropper' 89 | Vue.use(VueCropper) 90 | ``` 91 | 92 | 93 | `Vue2 CDN` 方式引入 94 | ```html 95 | 96 | ``` 97 | ```js 98 | Vue.use(window['vue-cropper'].default) 99 | ``` 100 | 101 | 102 | `nuxt` 引入方式 103 | ```js 104 | if(process.browser) { 105 | vueCropper = require('vue-cropper') 106 | Vue.use(vueCropper.default) 107 | } 108 | ``` 109 | 110 | ### 3. 代码中使用 111 | 112 | > **重要!** 需要关掉本地的 mock 服务, 不然图片转化会报错 113 | > **重要!** 需要使用外层容器包裹并设置宽高 114 | 115 | ```html 116 | 122 | ``` 123 | 124 | 125 | ## 二、文档 126 | 127 | ### 1. props 128 | 129 | > 目前还不知道什么原因项目里面开启 `mock` 会导致 file 报错,建议使用时关掉 `mock` 130 | 131 | 132 | 名称 | 功能 | 默认值 | 可选值 133 | --- | --- | --- | --- 134 | img | 裁剪图片的地址 | 空 | `url 地址`, `base64`, `blob` 135 | outputSize | 裁剪生成图片的质量 | `1` | 0.1 ~ 1 136 | outputType | 裁剪生成图片的格式 | jpg (jpg 需要传入jpeg) | `jpeg`, `png`, `webp` 137 | info | 裁剪框的大小信息 | `true` | `true`, `false` 138 | canScale | 图片是否允许滚轮缩放 | `true` | `true`, `false` 139 | autoCrop | 是否默认生成截图框 | `false` | `true`, `false` 140 | autoCropWidth | 默认生成截图框宽度 | 容器的 80% | 0 ~ max 141 | autoCropHeight | 默认生成截图框高度 | 容器的 80% | 0 ~ max 142 | fixed | 是否开启截图框宽高固定比例 | `false` | `true`, `false` 143 | fixedNumber | 截图框的宽高比例, 开启`fixed`生效 | `[1, 1]` | `[ 宽度 , 高度 ]` 144 | full | 是否输出原图比例的截图 | `false` | `true`, `false` 145 | fixedBox | 固定截图框大小 | 不允许改变 | `false` | `true`, `false` 146 | canMove | 上传图片是否可以移动 | `true` | `true`, `false` 147 | canMoveBox | 截图框能否拖动 | `true` | `true`, `false` 148 | original | 上传图片按照原始比例渲染 | `false` | `true`, `false` 149 | centerBox | 截图框是否被限制在图片里面 | `false` | `true`, `false` 150 | high | 是否按照设备的dpr 输出等比例图片 | `true` | `true`, `false` 151 | infoTrue | true 为展示真实输出图片宽高 `false` 展示看到的截图框宽高 | false | `true`, `false` 152 | maxImgSize | 限制图片最大宽度和高度 | `2000` | 0 ~ max 153 | enlarge | 图片根据截图框输出比例倍数 | `1` | 0 ~ max(建议不要太大不然会卡死的呢) 154 | mode | 图片默认渲染方式 | `contain` | `contain` , `cover`, `100px`, `100%` auto 155 | limitMinSize | 裁剪框限制最小区域 | 10 | Number, Array, String 156 | fillColor | 导出时背景颜色填充 | 空 | `#ffffff`, `white` 157 | 158 | ### 2. 可用回调方法 159 | 160 | - `@realTime` 实时预览事件 161 | - `@imgMoving` 图片移动回调函数 162 | - `@cropMoving` 截图框移动回调函数 163 | - `@imgLoad` 图片加载的回调, 返回结果 `success`, `error` 164 | 165 | #### @realTime 实时预览事件 166 | ```js 167 | realTime(data) { 168 | var previews = data 169 | var h = 0.5 170 | var w = 0.2 171 | 172 | this.previewStyle1 = { 173 | width: previews.w + "px", 174 | height: previews.h + "px", 175 | overflow: "hidden", 176 | margin: "0", 177 | zoom: h 178 | } 179 | 180 | this.previewStyle2 = { 181 | width: previews.w + "px", 182 | height: previews.h + "px", 183 | overflow: "hidden", 184 | margin: "0", 185 | zoom: w 186 | } 187 | 188 | // 固定为 100 宽度 189 | this.previewStyle3 = { 190 | width: previews.w + "px", 191 | height: previews.h + "px", 192 | overflow: "hidden", 193 | margin: "0", 194 | zoom: 100 / preview.w 195 | } 196 | 197 | // 固定为 100 高度 198 | this.previewStyle4 = { 199 | width: previews.w + "px", 200 | height: previews.h + "px", 201 | overflow: "hidden", 202 | margin: "0", 203 | zoom: 100 / preview.h 204 | } 205 | this.previews = data 206 | } 207 | ``` 208 | 209 | ```html 210 |
212 |
213 | 214 |
215 |
216 |

中等大小

217 |
218 |
219 | 220 |
221 |
222 | 223 |

迷你大小

224 |
225 |
226 | 227 |
228 |
229 | ``` 230 | 231 | 232 | #### @imgMoving 图片移动回调函数 233 | 234 | 返回的参数内容 235 | ```js 236 | { 237 | moving: true, // moving 是否在移动 238 | axis: { 239 | x1: 1, // 左上角 240 | x2: 1,// 右上角 241 | y1: 1,// 左下角 242 | y2: 1 // 右下角 243 | } 244 | } 245 | ``` 246 | 247 | #### @cropMoving 截图框移动回调函数 248 | 返回的参数内容 249 | ```js 250 | { 251 | moving: true, // moving 是否在移动 252 | axis: { 253 | x1: 1, // 左上角 254 | x2: 1,// 右上角 255 | y1: 1,// 左下角 256 | y2: 1 // 右下角 257 | } 258 | } 259 | ``` 260 | 261 | 262 | ### 2. 内置方法 和 属性 263 | 通过 `this.$refs.cropper` 调用 264 | 265 | **属性** 266 | 267 | 属性 | 说明 268 | --- | --- 269 | this.$refs.cropper.cropW | 截图框宽度 270 | this.$refs.cropper.cropH | 截图框高度 271 | 272 | 273 | **方法** 274 | 275 | 方法 | 说明 276 | --- | --- 277 | this.$refs.cropper.startCrop() | 开始截图 278 | this.$refs.cropper.stopCrop() | 停止截图 279 | this.$refs.cropper.clearCrop() | 清除截图 280 | this.$refs.cropper.changeScale() | 修改图片大小 正数为变大 负数变小 281 | this.$refs.cropper.getImgAxis() | 获取图片基于容器的坐标点 282 | this.$refs.cropper.getCropAxis() | 获取截图框基于容器的坐标点 283 | this.$refs.cropper.goAutoCrop | 自动生成截图框函数 284 | this.$refs.cropper.rotateRight() | 向右边旋转90度 285 | this.$refs.cropper.rotateLeft() | 向左边旋转90度 286 | 287 | **获取截图内容** 288 | 289 | 获取截图的 base64 数据 290 | 291 | ```js 292 | this.$refs.cropper.getCropData(data => { 293 | // do something 294 | console.log(data) 295 | }) 296 | ``` 297 | 298 | 获取截图的 blob 数据 299 | ```js 300 | this.$refs.cropper.getCropBlob(data => { 301 | // do something 302 | console.log(data) 303 | }) 304 | ``` 305 | 306 | 307 | 308 | 309 | 310 | ## 三、相关文章参考 311 | - [shn_ui - vue-cropper ](https://shnhz.github.io/shn-ui/#/component/vue-cropper) - 野宁新之助 312 | - [vue全家桶开发管理后台—裁切图片](https://blog.csdn.net/qq_30632003/article/details/79639346) - 麻球科技-菅双鹏 313 | - [Vue-cropper 图片裁剪的基本原理](https://www.cnblogs.com/tugenhua0707/p/8859291.html) - 龙恩0707 314 | - [关于昵称和头像的总结(模仿微信)](https://zhuanlan.zhihu.com/p/45746753) - 秋晨光 315 | - [vue-cropper-h5](https://github.com/2277419213/vue-cropper-h5) - 居里栈栈 316 | 317 | ## 四、交流 318 | 有什么意见,或者 bug,或者想一起开发 `vue-cropper`, 或者想一起开发其他插件 319 | ![image](https://github.com/user-attachments/assets/740d67f7-d924-458e-bc88-b76b8a395feb) 320 | 321 | 322 | 323 | 324 | 325 | 326 | -------------------------------------------------------------------------------- /next/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | test vue-cropper3 9 | 153 | 154 | 155 | 156 |
157 |
158 |
159 | 160 |
161 |
162 |

例子

163 |
164 | 169 |
170 |
171 | 172 | 173 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | download(base64) 186 | download(blob) 187 |
188 | 193 | 197 | 202 | 207 | 212 | 217 | 222 | 227 | 239 |
240 |
241 |
243 |
244 | 245 |
246 |
247 |
248 | 249 | 250 | 251 | 252 | 426 | 427 | 428 | -------------------------------------------------------------------------------- /english.md: -------------------------------------------------------------------------------- 1 | ## vue-cropper 2 | ### A simple picture clipping plugin for vue 3 | [preview](http://xyxiao.cn/vue-cropper/example/) 4 | [中文](https://github.com/xyxiao001/vue-cropper) 5 | 6 | ### Vue-cropper Related Articles Reference.。 7 | #### [vue全家桶开发管理后台—裁切图片](https://blog.csdn.net/qq_30632003/article/details/79639346)   作者: 麻球科技-菅双鹏 8 | #### [Vue-cropper 图片裁剪的基本原理](https://www.cnblogs.com/tugenhua0707/p/8859291.html) 作者: 龙恩0707 9 | 10 | ### vue-cropper communication.。 11 | ##### Any comments, or bugs or want to develop vue-cropper together, or want to develop other plugins together 12 | ![](https://qn-qn-kibey-static-cdn.app-echo.com/4C6FE9E2-3D06-402B-8F32-98B82BEBDD9F.png) 13 | 14 | # vue-cropper 15 | 16 | #### Install 17 | ``` 18 | 19 | npm install vue-cropper 20 | yarn add vue-cropper 21 | 22 | ``` 23 | 24 | #### Use 25 | 26 | ``` 27 | views 28 | 29 | import { VueCropper } from "vue-cropper" 30 | components: { 31 | VueCropper, 32 | }, 33 | 34 | main.js 35 | 36 | import VueCropper from "vue-cropper" 37 | 38 | Vue.use(VueCropper) 39 | 40 | cdn 41 | 42 | Vue.use(window['vue-cropper']) 43 | 44 | 50 | ``` 51 | 52 | 53 | 54 | ### not use npm or webpack 55 | [online example](https://codepen.io/xyxiao001/pen/wxwKGz) 56 | 57 | ### serve render nuxt, control: ssr: false 58 | ``` 59 | module.exports = { 60 | ... 61 | build: { 62 | vendor: [ 63 | 'vue-cropper 64 | ... 65 | plugins: [ 66 | { src: '~/plugins/vue-cropper', ssr: false } 67 | ] 68 | } 69 | } 70 | ``` 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 215 | 216 |
nameFeaturesDetailvalue
imgPicture addressnullurl address || base64 || blob
outputSizeCrop the quality of the generated image10.1 - 1
outputTypeCrop the format of the generated imagejpg (jpg need jpeg)jpeg || png || webp
infoCrop box size informationtruetrue || false
canScaleWhether the image allows the wheel to zoomtruetrue || false
autoCropWhether to generate a screenshot box by defaultfalsetrue || false
autoCropWidthDefault generation of screenshot frame widthparent's 80%0~max
autoCropHeightDefault generation of screenshot frame Heightparent's 80%0~max
fixedWhether to open the screenshot frame width and height fixed ratiotruetrue | false
fixedNumberThe aspect ratio of the screenshot box[1 : 1][width : height]
fullScreenshot of whether to output the original map scalefalsetrue | false
fixedBoxFixed screenshot frame size is not allowed to changefalsetrue | false
canMoveWhether the uploaded image can be movedtruetrue | false
canMoveBoxCan the screenshot box be dragged?truetrue | false
originalUpload images are rendered in raw scalefalsetrue | false
centerBoxIs the screenshot box restricted to the image?falsetrue | false
highIs it proportional to the dpi output of the device?truetrue | false
infoTrueTrue to show the true output image width and height false show the width of the screenshot framefalsetrue | false
maxImgSizeLimit the maximum width and height of the image20000-max
enlargePicture output ratio multiplier based on screenshots10-max(Don't be too big.)
modeimg render modecontaincontain , cover, 100px, 100% auto
217 | 218 | 219 | ### Built-in method Called by this.$refs.cropper 220 | ##### this.$refs.cropper.startCrop() Start the screenshot 221 | ##### this.$refs.cropper.stopCrop() Stop the screenshot 222 | ##### this.$refs.cropper.clearCrop() Clear screenshot 223 | ##### this.$refs.cropper.changeScale() Modify the image size. The positive number becomes larger. The negative number becomes smaller. 224 | ##### this.$refs.cropper.getImgAxis() Get the image based on the container's coordinate points 225 | ##### this.$refs.cropper.getCropAxis() Get the screenshot box based on the container's coordinate point 226 | ##### this.$refs.cropper.goAutoCrop Automatically generate screenshot box functions 227 | ##### this.$refs.cropper.rotateRight() Rotate 90 degrees to the right 228 | ##### this.$refs.cropper.rotateLeft() Rotate 90 degrees to the left 229 | 230 | #### Image loaded callback imgLoad returns the result success, error 231 | 232 | #### Get screenshot information 233 | this.$refs.cropper.cropW screenshot frame width 234 | 235 | this.$refs.cropper.cropH screenshot frame height 236 | ``` js 237 | // Get the base64 data of the screenshot 238 | this.$refs.cropper.getCropData((data) => { 239 |    // do something 240 |    Console.log(data) 241 | }) 242 | 243 | // Get the screenshot of the blob data 244 | this.$refs.cropper.getCropBlob((data) => { 245 |    // do something 246 |    Console.log(data) 247 | }) 248 | ### Preview 249 | ``` html 250 | @realTime="realTime" 251 | // Real time preview function 252 | realTime (data) { 253 | this.previews = data 254 | } 255 |
257 |
258 | 259 |
260 |
261 | = 262 | ``` 263 | 264 | #### Image Move Callback Function @imgMoving 265 | ``` 266 | data type 267 | { 268 | moving: true, // moving ismove 269 | axis: { 270 | x1: 1, // Upper left corner 271 | x2: 1,// Upper right corner 272 | y1: 1,// Lower left corner 273 | y2: 1 // Bottom right corner 274 | } 275 | } 276 | ``` 277 | 278 | #### Screenshot box move callback function @cropMoving 279 | ``` 280 | data type 281 | { 282 | moving: true, // moving 是否在移动 283 | axis: { 284 | x1: 1, // Upper left corner 285 | x2: 1,// Upper right corner 286 | y1: 1,// Lower left corner 287 | y2: 1 // Bottom right corner 288 | } 289 | } 290 | ``` 291 | 292 | 293 | ## Update log 294 | ### 0.56 295 | ``` 296 | Fix the problem of incorrect judgment of binding events 297 | Fix the issue that component removal does not unbind scroll events 298 | ``` 299 | 300 | ### 0.55 301 | ``` 302 | Fix the bug that ios version less than 13.4 does not handle image rotation 303 | ``` 304 | ### 0.54 305 | ` ` ` ` 306 | Remove log information 307 | Fix PC Safari low version 308 | ` ` ` ` 309 | ### 0.53 310 | ` ` ` ` 311 | Because chrome 81 kernel and IOS 13.5 fix the image rotation bug 312 | The plug-in will not process rotation by default in the new version of browser, and it will be processed automatically in the lower version of browser 313 | https://www.chromestatus.com/feature/6313474512650240 314 | 315 | ### 0.52 316 | ``` 317 | Recall the attribute of the minimum bullet box. There is a bug in the judgment of the dragging coordinate of the bullet box 318 | ``` 319 | 320 | ## 0.51 321 | `` ` 322 | Update the minimum attributes of the crop box, limit the minimum area, can pass more than 1 numbers and strings, limit the length and width are so large, can also pass arrays [90,90] 323 | limitMinSize: [Number, Array, String] 324 | `` ` 325 | 326 | ### 0.50 327 | Support picture empty 328 | Fix ie11 ie10 not working 329 | Fix the bug that URL.createObjectURL is not destroyed after creation 330 | Add screenshot box to modify trigger event 331 | this. $ emit ('change-crop-size', { 332 |    width: this.cropW, 333 |    height: this.cropH 334 | }) 335 | 336 | 337 | ### 0.49 338 | Fix the default event of the scroll wheel 339 | Fix the issue of event trigger in html static file 340 | 341 | ### 0.48 342 | Fix display bug of mode attribute contain and cover 343 | 344 | Fix the problem of cross domain display of base64 pictures under ios 345 | ### 0.47 346 | Fix the problem that does not trigger preview for the first time 347 | New image rendering mode function 348 | 349 | ### 0.46 350 | Fix image rotation bug 351 | Fix some bugs displayed 352 | 353 | ### 0.45 354 | 355 | Add multiples using enlarge 356 | 357 | You can output clipping boxes and other proportional images. 358 | 359 | 360 | 361 | Thank you for your contribution from [https://github.com/hzsrc]. 362 | 363 | Add preview box to various proportions, and restore image screenshots decimal problem. 364 | 365 | ### 0.44 366 | fix required 367 | ``` 368 | Repairing the way of introduction 369 | import { VueCropper } from vue-cropper 370 | components: { 371 | VueCropper, 372 | }, 373 | 374 | main.js 375 | import VueCropper from vue-cropper 376 | 377 | Vue.use(vueCropper) 378 | 379 | cdn 380 | 381 | Vue.use(window['vue-cropper']) 382 | ``` 383 | 384 | 385 | ### V0.43 386 | 387 | Peel off EXIF's dependency library, add exfi-min.js to reduce code size 45.9k = 37k 388 | 389 | Build upgrade webpack4 upgrade 390 | 391 | ` ` ` ` ` ` ` ` ` '. 392 | 393 | Add Vue install method = "npm: Vue.use (VueCropper) web: Vue.use (window['vue-cropper'])" 394 | 395 | ` ` ` ` ` ` ` ` ` '. 396 | 397 | 398 | 399 | 400 | ### V0.42 401 | 402 | Repair screenshots because of the problem of removing decimal points. 403 | 404 | 405 | 406 | ### V0.41 407 | 408 | Repair boundary problem of screenshots 409 | 410 | 411 | 412 | 413 | ### V0.40 414 | 415 | The way to repair orientation 416 | 417 | Thanks for the contribution of [Felipe Mengatto] (https://github.com/felipemengatto). 418 | 419 | ### v0.40 420 | fix orientation handel 421 | Thanks for the contribution of [Felipe Mengatto] (https://github.com/felipemengatto) 422 | 423 | 424 | ### v0.39 425 | 426 | Fix problems caused by different orientation values 427 | Thanks for the contribution of [Felipe Mengatto] (https://github.com/felipemengatto) 428 | 429 | 430 | ### v0.38 431 | ``` 432 | Modify coordinate feedback problem 433 | ``` 434 | 435 | 436 | ### v0.37 437 | ``` 438 | Fix screenshot of centerBox out of 1px issue 439 | Add screenshot Image move trigger event 440 | ``` 441 | 442 | ### v0.36 443 | ``` 444 | Fix rotation automatically generates screenshot box error 445 | Modify autocrop to dynamically generate screenshot boxes 446 | ``` 447 | 448 | ### v0.35 449 | ``` 450 | Fix other images without compression issues 451 | ``` 452 | 453 | ### v0.34 454 | ``` provides a solution for mobile crashes 455 | Modify maxImgSize to 2000 456 | ``` 457 | 458 | ### v0.33 459 | ``` provides a solution for mobile crashes 460 | maxImgSize limits the maximum width and height of the image to 2000px by default. 461 | ``` 462 | 463 | ### v0.32 464 | ``` 465 | Add screenshot box information display 466 | infoTrue true to show the true output image width and height false show the width of the screenshot box 467 | ``` 468 | 469 | ### v0.30 470 | ``` 471 | Added image coordinate function this.$refs.cropper.getImgAxis() 472 | Added the capture box coordinate function this.$refs.cropper.getCropAxis() 473 | Added compatibility with HD devices high 474 | Added screenshot box to limit the function within the image centerbox 475 | Added automatic generation of screenshot box function this.$refs.cropper.goAutoCrop 476 | ``` 477 | 478 | ### v0.29 479 | Added callback for image loading imgLoad returns result success, error 480 | ### v0.28 481 | Fix the screenshot box fixed The screenshot box will affect the original image movement Zoom 482 | ### v0.27 483 | Mouse scaling problem optimization 484 | Img max-width style optimization 485 | New attribute 486 | CanMove can move pictures by default is yes 487 | CanMoveBox move the screenshot box by default? 488 | Original Whether to render in the original scale of the image Default is No 489 | 490 | 491 | ### v0.26 492 | Fix Firefox browser mouse zoom problem 493 | 494 | ### v0.25 495 | Fix image may not show 496 | 497 | ### v0.24 498 | Fix ios photo rotation Screenshot problem Add auto fix image Screenshot preview code change, modify default upload image as blob preview 499 | ``` html 500 | realTime (data) { 501 |   this.previews = data 502 | } 503 |
505 |   
506 |      507 |   
508 |
509 | ``` 510 | 511 | 512 | ### v0.23 513 | Small optimization 514 | ### v0.22 515 |  New modified image size function called by this.$refs.cropper.changeScale 516 | 517 | ### v0.21 518 | Added fixed screenshot frame size fiexdBox (Note: It is best to use the automatic generation of screenshot box) 519 | 520 | ### v0.20 521 | Added output original image scale screenshot props name full, fix zoom image too large sensitivity problem 522 | 523 | ### v0.19 524 | Add image rotation to fix mac wheel over-sensitive 525 | ``` js 526 | this.$refs.cropper.rotateRight() // Rotate 90 degrees to the right 527 | this.$refs.cropper.rotateLeft() // Rotate 90 degrees to the left 528 | ``` 529 | 530 | ### v0.18 531 | Fix default build screenshot box over container error 532 | ### v0.17 533 | Fix blob data acquisition error 534 | ### v0.15 535 | Add mobile phone gesture zoom 536 | ``` 537 | canScale: true 538 | ``` 539 | 540 | ### v0.13 541 | Add preview 542 | ``` html 543 | @realTime="realTime" 544 | // Real time preview function 545 | realTime (data) { 546 |   this.previews = data 547 | } 548 |
550 |   
551 |      552 |   
553 |
554 | ``` 555 | -------------------------------------------------------------------------------- /docs/vue2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | test vue-cropper@vue2 9 | 153 | 154 | 155 | 156 |
157 |
158 |
159 | 160 |
161 |
162 |

例子

163 |
164 | 165 |
166 |
167 | 168 | 169 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | download(base64) 182 | download(blob) 183 |
184 | 189 | 203 | 207 | 212 | 217 | 222 | 227 | 232 | 237 | 242 | 247 | 252 | 257 | 262 | 267 | 272 | 276 |

输出图片格式

277 | 282 |
283 |
284 |
286 |
287 | 288 |
289 |
290 |
291 | 292 | 293 | 294 | 473 | 474 | 475 | -------------------------------------------------------------------------------- /docs/vue3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | test vue-cropper@vue3 9 | 10 | 154 | 155 | 156 | 157 |
158 |
159 |
160 | 161 |
162 |
163 |

例子

164 |
165 | 166 |
167 |
168 | 169 | 170 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | download(base64) 183 | download(blob) 184 |
185 | 190 | 204 | 208 | 213 | 218 | 223 | 228 | 233 | 238 | 243 | 248 | 253 | 258 | 263 | 268 | 273 | 277 |

输出图片格式

278 | 283 |
284 |
285 |
287 |
288 | 289 |
290 |
291 |
292 | 293 | 294 | 295 | 476 | 477 | 478 | -------------------------------------------------------------------------------- /next/src/App.vue: -------------------------------------------------------------------------------- 1 | 209 | 210 | 488 | 489 | 663 | -------------------------------------------------------------------------------- /next/dist/vue-cropper.umd.js: -------------------------------------------------------------------------------- 1 | (function(C,l){typeof exports=="object"&&typeof module<"u"?l(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],l):(C=typeof globalThis<"u"?globalThis:C||self,l(C["vue-cropper"]={},C.Vue))})(this,function(C,l){"use strict";var y=document.createElement("style");y.textContent=`.vue-cropper[data-v-a742df44]{position:relative;width:100%;height:100%;box-sizing:border-box;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;direction:ltr;touch-action:none;text-align:left;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-box[data-v-a742df44],.cropper-box-canvas[data-v-a742df44],.cropper-drag-box[data-v-a742df44],.cropper-crop-box[data-v-a742df44],.cropper-face[data-v-a742df44]{position:absolute;top:0;right:0;bottom:0;left:0;-webkit-user-select:none;user-select:none}.cropper-box-canvas img[data-v-a742df44]{position:relative;text-align:left;-webkit-user-select:none;user-select:none;transform:none;max-width:none;max-height:none}.cropper-box[data-v-a742df44]{overflow:hidden}.cropper-move[data-v-a742df44]{cursor:move}.cropper-crop[data-v-a742df44]{cursor:crosshair}.cropper-modal[data-v-a742df44]{background:rgba(0,0,0,.5)}.cropper-view-box[data-v-a742df44]{display:block;overflow:hidden;width:100%;height:100%;outline:1px solid #39f;outline-color:#3399ffbf;-webkit-user-select:none;user-select:none}.cropper-view-box img[data-v-a742df44]{-webkit-user-select:none;user-select:none;text-align:left;max-width:none;max-height:none}.cropper-face[data-v-a742df44]{top:0;left:0;background-color:#fff;opacity:.1}.crop-info[data-v-a742df44]{position:absolute;left:0;min-width:65px;text-align:center;color:#fff;line-height:20px;background-color:#000c;font-size:12px}.crop-line[data-v-a742df44]{position:absolute;display:block;width:100%;height:100%;opacity:.1}.line-w[data-v-a742df44]{top:-3px;left:0;height:5px;cursor:n-resize}.line-a[data-v-a742df44]{top:0;left:-3px;width:5px;cursor:w-resize}.line-s[data-v-a742df44]{bottom:-3px;left:0;height:5px;cursor:s-resize}.line-d[data-v-a742df44]{top:0;right:-3px;width:5px;cursor:e-resize}.crop-point[data-v-a742df44]{position:absolute;width:8px;height:8px;opacity:.75;background-color:#39f;border-radius:100%}.point1[data-v-a742df44]{top:-4px;left:-4px;cursor:nw-resize}.point2[data-v-a742df44]{top:-5px;left:50%;margin-left:-3px;cursor:n-resize}.point3[data-v-a742df44]{top:-4px;right:-4px;cursor:ne-resize}.point4[data-v-a742df44]{top:50%;left:-4px;margin-top:-3px;cursor:w-resize}.point5[data-v-a742df44]{top:50%;right:-4px;margin-top:-3px;cursor:e-resize}.point6[data-v-a742df44]{bottom:-5px;left:-4px;cursor:sw-resize}.point7[data-v-a742df44]{bottom:-5px;left:50%;margin-left:-3px;cursor:s-resize}.point8[data-v-a742df44]{bottom:-5px;right:-4px;cursor:se-resize}@media screen and (max-width: 500px){.crop-point[data-v-a742df44]{position:absolute;width:20px;height:20px;opacity:.45;background-color:#39f;border-radius:100%}.point1[data-v-a742df44]{top:-10px;left:-10px}.point2[data-v-a742df44],.point4[data-v-a742df44],.point5[data-v-a742df44],.point7[data-v-a742df44]{display:none}.point3[data-v-a742df44]{top:-10px;right:-10px}.point4[data-v-a742df44]{top:0;left:0}.point6[data-v-a742df44]{bottom:-10px;left:-10px}.point8[data-v-a742df44]{bottom:-10px;right:-10px}} 2 | `,document.head.appendChild(y);const O={};O.getData=t=>new Promise((e,i)=>{let s={};W(t).then(r=>{s.arrayBuffer=r;try{s.orientation=S(r)}catch{s.orientation=-1}e(s)}).catch(r=>{i(r)})});function W(t){let e=null;return new Promise((i,s)=>{if(t.src)if(/^data\:/i.test(t.src))e=Y(t.src),i(e);else if(/^blob\:/i.test(t.src)){var r=new FileReader;r.onload=function(h){e=h.target.result,i(e)},X(t.src,function(h){r.readAsArrayBuffer(h)})}else{var o=new XMLHttpRequest;o.onload=function(){if(this.status==200||this.status===0)e=o.response,i(e);else throw"Could not load image";o=null},o.open("GET",t.src,!0),o.responseType="arraybuffer",o.send(null)}else s("img error")})}function X(t,e){var i=new XMLHttpRequest;i.open("GET",t,!0),i.responseType="blob",i.onload=function(s){(this.status==200||this.status===0)&&e(this.response)},i.send()}function Y(t,e){e=e||t.match(/^data\:([^\;]+)\;base64,/mi)[1]||"",t=t.replace(/^data\:([^\;]+)\;base64,/gmi,"");for(var i=atob(t),s=i.length%2==0?i.length:i.length+1,r=new ArrayBuffer(s),o=new Uint16Array(r),h=0;h=8&&(p=o+h)))),p){for(i=e.getUint16(p,a),f=0;f{const i=t.__vccOpts||t;for(const[s,r]of e)i[s]=r;return i},I=l.defineComponent({data:function(){return{w:0,h:0,scale:1,x:0,y:0,loading:!0,trueWidth:0,trueHeight:0,move:!0,moveX:0,moveY:0,crop:!1,cropping:!1,cropW:0,cropH:0,cropOldW:0,cropOldH:0,canChangeX:!1,canChangeY:!1,changeCropTypeX:1,changeCropTypeY:1,cropX:0,cropY:0,cropChangeX:0,cropChangeY:0,cropOffsertX:0,cropOffsertY:0,support:"",touches:[],touchNow:!1,rotate:0,isIos:!1,orientation:0,imgs:"",coe:.2,scaling:!1,scalingSet:"",coeStatus:"",isCanShow:!0,imgIsQqualCrop:!1}},props:{img:{type:[String,Blob,null,File],default:""},outputSize:{type:Number,default:1},outputType:{type:String,default:"jpeg"},info:{type:Boolean,default:!0},canScale:{type:Boolean,default:!0},autoCrop:{type:Boolean,default:!1},autoCropWidth:{type:[Number,String],default:0},autoCropHeight:{type:[Number,String],default:0},fixed:{type:Boolean,default:!1},fixedNumber:{type:Array,default:()=>[1,1]},fixedBox:{type:Boolean,default:!1},full:{type:Boolean,default:!1},canMove:{type:Boolean,default:!0},canMoveBox:{type:Boolean,default:!0},original:{type:Boolean,default:!1},centerBox:{type:Boolean,default:!1},high:{type:Boolean,default:!0},infoTrue:{type:Boolean,default:!1},maxImgSize:{type:[Number,String],default:2e3},enlarge:{type:[Number,String],default:1},preW:{type:[Number,String],default:0},mode:{type:String,default:"contain"},limitMinSize:{type:[Number,Array,String],default:()=>10,validator:function(t){return Array.isArray(t)?Number(t[0])>=0&&Number(t[1])>=0:Number(t)>=0}},fillColor:{type:String,default:""}},computed:{cropInfo(){let t={};if(t.top=this.cropOffsertY>21?"-21px":"0px",t.width=this.cropW>0?this.cropW:0,t.height=this.cropH>0?this.cropH:0,this.infoTrue){let e=1;this.high&&!this.full&&(e=window.devicePixelRatio),this.enlarge!==1&!this.full&&(e=Math.abs(Number(this.enlarge))),t.width=t.width*e,t.height=t.height*e,this.full&&(t.width=t.width/this.scale,t.height=t.height/this.scale)}return t.width=t.width.toFixed(0),t.height=t.height.toFixed(0),t},isIE(){return!!window.ActiveXObject||"ActiveXObject"in window},passive(){return this.isIE?null:{passive:!1}},isRotateRightOrLeft(){return[1,-1,3,-3].includes(this.rotate)}},watch:{img(){this.checkedImg()},imgs(t){t!==""&&this.reload()},cropW(){this.showPreview()},cropH(){this.showPreview()},cropOffsertX(){this.showPreview()},cropOffsertY(){this.showPreview()},scale(t,e){this.showPreview()},x(){this.showPreview()},y(){this.showPreview()},autoCrop(t){t&&this.goAutoCrop()},autoCropWidth(){this.autoCrop&&this.goAutoCrop()},autoCropHeight(){this.autoCrop&&this.goAutoCrop()},mode(){this.checkedImg()},rotate(){this.showPreview(),this.autoCrop?this.goAutoCrop(this.cropW,this.cropH):(this.cropW>0||this.cropH>0)&&this.goAutoCrop(this.cropW,this.cropH)}},methods:{getVersion(t){var e=navigator.userAgent.split(" "),i="";let s=0;const r=new RegExp(t,"i");for(var o=0;o=81)e=-1;else if(this.getVersion("safari")[0]>=605){const h=this.getVersion("version");h[0]>13&&h[1]>1&&(e=-1)}else{const h=navigator.userAgent.toLowerCase().match(/cpu iphone os (.*?) like mac os/);if(h){let a=h[1];a=a.split("_"),(a[0]>13||a[0]>=13&&a[1]>=4)&&(e=-1)}}let r=document.createElement("canvas"),o=r.getContext("2d");switch(o.save(),e){case 2:r.width=i,r.height=s,o.translate(i,0),o.scale(-1,1);break;case 3:r.width=i,r.height=s,o.translate(i/2,s/2),o.rotate(180*Math.PI/180),o.translate(-i/2,-s/2);break;case 4:r.width=i,r.height=s,o.translate(0,s),o.scale(1,-1);break;case 5:r.height=i,r.width=s,o.rotate(.5*Math.PI),o.scale(1,-1);break;case 6:r.width=s,r.height=i,o.translate(s/2,i/2),o.rotate(90*Math.PI/180),o.translate(-i/2,-s/2);break;case 7:r.height=i,r.width=s,o.rotate(.5*Math.PI),o.translate(i,-s),o.scale(-1,1);break;case 8:r.height=i,r.width=s,o.translate(s/2,i/2),o.rotate(-90*Math.PI/180),o.translate(-i/2,-s/2);break;default:r.width=i,r.height=s}o.drawImage(t,0,0,i,s),o.restore(),r.toBlob(h=>{let a=URL.createObjectURL(h);URL.revokeObjectURL(this.imgs),this.imgs=a},"image/"+this.outputType,1)},checkedImg(){if(this.img===null||this.img===""){this.imgs="",this.clearCrop();return}this.loading=!0,this.scale=1,this.rotate=0,this.imgIsQqualCrop=!1,this.clearCrop();let t=new Image;if(t.onload=()=>{if(this.img==="")return this.$emit("img-load",new Error("图片不能为空")),!1;let i=t.width,s=t.height;O.getData(t).then(r=>{this.orientation=r.orientation||1;let o=Number(this.maxImgSize);if(!this.orientation&&io&&(s=s/i*o,i=o),s>o&&(i=i/s*o,s=o),this.checkOrientationImage(t,this.orientation,i,s)}).catch(r=>{this.$emit("img-load","error"),this.$emit("img-load-error",r)})},t.onerror=i=>{this.$emit("img-load","error"),this.$emit("img-load-error",i)},this.img.substr(0,4)!=="data"&&(t.crossOrigin=""),this.isIE){var e=new XMLHttpRequest;e.onload=function(){var i=URL.createObjectURL(this.response);t.src=i},e.open("GET",this.img,!0),e.responseType="blob",e.send()}else t.src=this.img},startMove(t){if(t.preventDefault(),this.move&&!this.crop){if(!this.canMove)return!1;this.moveX=("clientX"in t?t.clientX:t.touches[0].clientX)-this.x,this.moveY=("clientY"in t?t.clientY:t.touches[0].clientY)-this.y,t.touches?(window.addEventListener("touchmove",this.moveImg),window.addEventListener("touchend",this.leaveImg),t.touches.length==2&&(this.touches=t.touches,window.addEventListener("touchmove",this.touchScale),window.addEventListener("touchend",this.cancelTouchScale))):(window.addEventListener("mousemove",this.moveImg),window.addEventListener("mouseup",this.leaveImg)),this.$emit("img-moving",{moving:!0,axis:this.getImgAxis()})}else this.cropping=!0,window.addEventListener("mousemove",this.createCrop),window.addEventListener("mouseup",this.endCrop),window.addEventListener("touchmove",this.createCrop),window.addEventListener("touchend",this.endCrop),this.cropOffsertX=t.offsetX?t.offsetX:t.touches[0].pageX-this.$refs.cropper.offsetLeft,this.cropOffsertY=t.offsetY?t.offsetY:t.touches[0].pageY-this.$refs.cropper.offsetTop,this.cropX="clientX"in t?t.clientX:t.touches[0].clientX,this.cropY="clientY"in t?t.clientY:t.touches[0].clientY,this.cropChangeX=this.cropOffsertX,this.cropChangeY=this.cropOffsertY,this.cropW=0,this.cropH=0},touchScale(t){t.preventDefault();let e=this.scale;var i={x:this.touches[0].clientX,y:this.touches[0].clientY},s={x:t.touches[0].clientX,y:t.touches[0].clientY},r={x:this.touches[1].clientX,y:this.touches[1].clientY},o={x:t.touches[1].clientX,y:t.touches[1].clientY},h=Math.sqrt(Math.pow(i.x-r.x,2)+Math.pow(i.y-r.y,2)),a=Math.sqrt(Math.pow(s.x-o.x,2)+Math.pow(s.y-o.y,2)),n=a-h,c=1;c=c/this.trueWidth>c/this.trueHeight?c/this.trueHeight:c/this.trueWidth,c=c>.1?.1:c;var p=c*n;if(!this.touchNow){if(this.touchNow=!0,n>0?e+=Math.abs(p):n<0&&e>Math.abs(p)&&(e-=Math.abs(p)),this.touches=t.touches,setTimeout(()=>{this.touchNow=!1},8),!this.checkoutImgAxis(this.x,this.y,e))return!1;this.scale=e}},cancelTouchScale(t){window.removeEventListener("touchmove",this.touchScale)},moveImg(t){if(t.preventDefault(),t.touches&&t.touches.length===2)return this.touches=t.touches,window.addEventListener("touchmove",this.touchScale),window.addEventListener("touchend",this.cancelTouchScale),window.removeEventListener("touchmove",this.moveImg),!1;let e="clientX"in t?t.clientX:t.touches[0].clientX,i="clientY"in t?t.clientY:t.touches[0].clientY,s,r;s=e-this.moveX,r=i-this.moveY,this.$nextTick(()=>{if(this.centerBox){let o=this.getImgAxis(s,r,this.scale),h=this.getCropAxis(),a=this.trueHeight*this.scale,n=this.trueWidth*this.scale,c,p,d,f;switch(this.rotate){case 1:case-1:case 3:case-3:c=this.cropOffsertX-this.trueWidth*(1-this.scale)/2+(a-n)/2,p=this.cropOffsertY-this.trueHeight*(1-this.scale)/2+(n-a)/2,d=c-a+this.cropW,f=p-n+this.cropH;break;default:c=this.cropOffsertX-this.trueWidth*(1-this.scale)/2,p=this.cropOffsertY-this.trueHeight*(1-this.scale)/2,d=c-n+this.cropW,f=p-a+this.cropH;break}o.x1>=h.x1&&(s=c),o.y1>=h.y1&&(r=p),o.x2<=h.x2&&(s=d),o.y2<=h.y2&&(r=f)}this.x=s,this.y=r,this.$emit("img-moving",{moving:!0,axis:this.getImgAxis()})})},leaveImg(t){window.removeEventListener("mousemove",this.moveImg),window.removeEventListener("touchmove",this.moveImg),window.removeEventListener("mouseup",this.leaveImg),window.removeEventListener("touchend",this.leaveImg),this.$emit("img-moving",{moving:!1,axis:this.getImgAxis()})},scaleImg(){this.canScale&&window.addEventListener(this.support,this.changeSize,this.passive)},cancelScale(){this.canScale&&window.removeEventListener(this.support,this.changeSize)},changeSize(t){t.preventDefault();let e=this.scale;var i=t.deltaY||t.wheelDelta,s=navigator.userAgent.indexOf("Firefox");i=s>0?i*30:i,this.isIE&&(i=-i);var r=this.coe;r=r/this.trueWidth>r/this.trueHeight?r/this.trueHeight:r/this.trueWidth;var o=r*i;o<0?e+=Math.abs(o):e>Math.abs(o)&&(e-=Math.abs(o));let h=o<0?"add":"reduce";if(h!==this.coeStatus&&(this.coeStatus=h,this.coe=.2),this.scaling||(this.scalingSet=setTimeout(()=>{this.scaling=!1,this.coe=this.coe+=.01},50)),this.scaling=!0,!this.checkoutImgAxis(this.x,this.y,e))return!1;this.scale=e},changeScale(t){let e=this.scale;t=t||1;var i=20;if(i=i/this.trueWidth>i/this.trueHeight?i/this.trueHeight:i/this.trueWidth,t=t*i,t>0?e+=Math.abs(t):e>Math.abs(t)&&(e-=Math.abs(t)),!this.checkoutImgAxis(this.x,this.y,e))return!1;this.scale=e},createCrop(t){t.preventDefault();var e="clientX"in t?t.clientX:t.touches?t.touches[0].clientX:0,i="clientY"in t?t.clientY:t.touches?t.touches[0].clientY:0;this.$nextTick(()=>{var s=e-this.cropX,r=i-this.cropY;if(s>0?(this.cropW=s+this.cropChangeX>this.w?this.w-this.cropChangeX:s,this.cropOffsertX=this.cropChangeX):(this.cropW=this.w-this.cropChangeX+Math.abs(s)>this.w?this.cropChangeX:Math.abs(s),this.cropOffsertX=this.cropChangeX+s>0?this.cropChangeX+s:0),!this.fixed)r>0?(this.cropH=r+this.cropChangeY>this.h?this.h-this.cropChangeY:r,this.cropOffsertY=this.cropChangeY):(this.cropH=this.h-this.cropChangeY+Math.abs(r)>this.h?this.cropChangeY:Math.abs(r),this.cropOffsertY=this.cropChangeY+r>0?this.cropChangeY+r:0);else{var o=this.cropW/this.fixedNumber[0]*this.fixedNumber[1];o+this.cropOffsertY>this.h?(this.cropH=this.h-this.cropOffsertY,this.cropW=this.cropH/this.fixedNumber[1]*this.fixedNumber[0],s>0?this.cropOffsertX=this.cropChangeX:this.cropOffsertX=this.cropChangeX-this.cropW):this.cropH=o,this.cropOffsertY=this.cropOffsertY}})},changeCropSize(t,e,i,s,r){t.preventDefault(),window.addEventListener("mousemove",this.changeCropNow),window.addEventListener("mouseup",this.changeCropEnd),window.addEventListener("touchmove",this.changeCropNow),window.addEventListener("touchend",this.changeCropEnd),this.canChangeX=e,this.canChangeY=i,this.changeCropTypeX=s,this.changeCropTypeY=r,this.cropX="clientX"in t?t.clientX:t.touches[0].clientX,this.cropY="clientY"in t?t.clientY:t.touches[0].clientY,this.cropOldW=this.cropW,this.cropOldH=this.cropH,this.cropChangeX=this.cropOffsertX,this.cropChangeY=this.cropOffsertY,this.fixed&&this.canChangeX&&this.canChangeY&&(this.canChangeY=0),this.$emit("change-crop-size",{width:this.cropW,height:this.cropH})},changeCropNow(t){t.preventDefault();var e="clientX"in t?t.clientX:t.touches?t.touches[0].clientX:0,i="clientY"in t?t.clientY:t.touches?t.touches[0].clientY:0;let s=this.w,r=this.h,o=0,h=0;if(this.centerBox){let c=this.getImgAxis(),p=c.x2,d=c.y2;o=c.x1>0?c.x1:0,h=c.y1>0?c.y1:0,s>p&&(s=p),r>d&&(r=d)}const[a,n]=this.checkCropLimitSize();this.$nextTick(()=>{var c=e-this.cropX,p=i-this.cropY;if(this.canChangeX&&(this.changeCropTypeX===1?this.cropOldW-c0?(this.cropW=s-this.cropChangeX-c<=s-o?this.cropOldW-c:this.cropOldW+this.cropChangeX-o,this.cropOffsertX=s-this.cropChangeX-c<=s-o?this.cropChangeX+c:o):(this.cropW=Math.abs(c)+this.cropChangeX<=s?Math.abs(c)-this.cropOldW:s-this.cropOldW-this.cropChangeX,this.cropOffsertX=this.cropChangeX+this.cropOldW):this.changeCropTypeX===2&&(this.cropOldW+c0?(this.cropW=this.cropOldW+c+this.cropOffsertX<=s?this.cropOldW+c:s-this.cropOffsertX,this.cropOffsertX=this.cropChangeX):(this.cropW=s-this.cropChangeX+Math.abs(c+this.cropOldW)<=s-o?Math.abs(c+this.cropOldW):this.cropChangeX-o,this.cropOffsertX=s-this.cropChangeX+Math.abs(c+this.cropOldW)<=s-o?this.cropChangeX-Math.abs(c+this.cropOldW):o))),this.canChangeY&&(this.changeCropTypeY===1?this.cropOldH-p0?(this.cropH=r-this.cropChangeY-p<=r-h?this.cropOldH-p:this.cropOldH+this.cropChangeY-h,this.cropOffsertY=r-this.cropChangeY-p<=r-h?this.cropChangeY+p:h):(this.cropH=Math.abs(p)+this.cropChangeY<=r?Math.abs(p)-this.cropOldH:r-this.cropOldH-this.cropChangeY,this.cropOffsertY=this.cropChangeY+this.cropOldH):this.changeCropTypeY===2&&(this.cropOldH+p0?(this.cropH=this.cropOldH+p+this.cropOffsertY<=r?this.cropOldH+p:r-this.cropOffsertY,this.cropOffsertY=this.cropChangeY):(this.cropH=r-this.cropChangeY+Math.abs(p+this.cropOldH)<=r-h?Math.abs(p+this.cropOldH):this.cropChangeY-h,this.cropOffsertY=r-this.cropChangeY+Math.abs(p+this.cropOldH)<=r-h?this.cropChangeY-Math.abs(p+this.cropOldH):h))),this.canChangeX&&this.fixed){var d=this.cropW/this.fixedNumber[0]*this.fixedNumber[1];dr?(this.cropH=r-this.cropOffsertY,this.cropW=this.cropH/this.fixedNumber[1]*this.fixedNumber[0],this.changeCropTypeX===1&&(this.cropOffsertX=this.cropChangeX+(this.cropOldW-this.cropW))):this.cropH=d}if(this.canChangeY&&this.fixed){var f=this.cropH/this.fixedNumber[1]*this.fixedNumber[0];fs?(this.cropW=s-this.cropOffsertX,this.cropH=this.cropW/this.fixedNumber[0]*this.fixedNumber[1]):this.cropW=f}})},checkCropLimitSize(){let{cropW:t,cropH:e,limitMinSize:i}=this,s=new Array;return Array.isArray(i)?s=i:s=[i,i],t=parseFloat(s[0]),e=parseFloat(s[1]),[t,e]},changeCropEnd(t){window.removeEventListener("mousemove",this.changeCropNow),window.removeEventListener("mouseup",this.changeCropEnd),window.removeEventListener("touchmove",this.changeCropNow),window.removeEventListener("touchend",this.changeCropEnd)},calculateSize(t,e,i,s,r,o){const h=t/e;let a=r,n=o;return athis.cropW&&(this.cropW=i,this.cropOffsertX+i>this.w&&(this.cropOffsertX=this.w-i)),s>this.cropH&&(this.cropH=s,this.cropOffsertY+s>this.h&&(this.cropOffsertY=this.h-s)),window.removeEventListener("mousemove",this.createCrop),window.removeEventListener("mouseup",this.endCrop),window.removeEventListener("touchmove",this.createCrop),window.removeEventListener("touchend",this.endCrop)},startCrop(){this.crop=!0},stopCrop(){this.crop=!1},clearCrop(){this.cropping=!1,this.cropW=0,this.cropH=0},cropMove(t){if(t.preventDefault(),!this.canMoveBox)return this.crop=!1,this.startMove(t),!1;if(t.touches&&t.touches.length===2)return this.crop=!1,this.startMove(t),this.leaveCrop(),!1;window.addEventListener("mousemove",this.moveCrop),window.addEventListener("mouseup",this.leaveCrop),window.addEventListener("touchmove",this.moveCrop),window.addEventListener("touchend",this.leaveCrop);let e="clientX"in t?t.clientX:t.touches[0].clientX,i="clientY"in t?t.clientY:t.touches[0].clientY,s,r;s=e-this.cropOffsertX,r=i-this.cropOffsertY,this.cropX=s,this.cropY=r,this.$emit("crop-moving",{moving:!0,axis:this.getCropAxis()})},moveCrop(t,e){let i=0,s=0;t&&(t.preventDefault(),i="clientX"in t?t.clientX:t.touches[0].clientX,s="clientY"in t?t.clientY:t.touches[0].clientY),this.$nextTick(()=>{let r,o,h=i-this.cropX,a=s-this.cropY;if(e&&(h=this.cropOffsertX,a=this.cropOffsertY),h<=0?r=0:h+this.cropW>this.w?r=this.w-this.cropW:r=h,a<=0?o=0:a+this.cropH>this.h?o=this.h-this.cropH:o=a,this.centerBox){let n=this.getImgAxis();r<=n.x1&&(r=n.x1),r+this.cropW>n.x2&&(r=n.x2-this.cropW),o<=n.y1&&(o=n.y1),o+this.cropH>n.y2&&(o=n.y2-this.cropH)}this.cropOffsertX=r,this.cropOffsertY=o,this.$emit("crop-moving",{moving:!0,axis:this.getCropAxis()})})},getImgAxis(t,e,i){t=t||this.x,e=e||this.y,i=i||this.scale;let s={x1:0,x2:0,y1:0,y2:0},r=this.trueWidth*i,o=this.trueHeight*i;switch(this.rotate){case 0:s.x1=t+this.trueWidth*(1-i)/2,s.x2=s.x1+this.trueWidth*i,s.y1=e+this.trueHeight*(1-i)/2,s.y2=s.y1+this.trueHeight*i;break;case 1:case-1:case 3:case-3:s.x1=t+this.trueWidth*(1-i)/2+(r-o)/2,s.x2=s.x1+this.trueHeight*i,s.y1=e+this.trueHeight*(1-i)/2+(o-r)/2,s.y2=s.y1+this.trueWidth*i;break;default:s.x1=t+this.trueWidth*(1-i)/2,s.x2=s.x1+this.trueWidth*i,s.y1=e+this.trueHeight*(1-i)/2,s.y2=s.y1+this.trueHeight*i;break}return s},getCropAxis(){let t={x1:0,x2:0,y1:0,y2:0};return t.x1=this.cropOffsertX,t.x2=t.x1+this.cropW,t.y1=this.cropOffsertY,t.y2=t.y1+this.cropH,t},leaveCrop(t){window.removeEventListener("mousemove",this.moveCrop),window.removeEventListener("mouseup",this.leaveCrop),window.removeEventListener("touchmove",this.moveCrop),window.removeEventListener("touchend",this.leaveCrop),this.$emit("crop-moving",{moving:!1,axis:this.getCropAxis()})},getCropChecked(t){let e=document.createElement("canvas"),i=e.getContext("2d"),s=new Image,r=this.rotate,o=this.trueWidth,h=this.trueHeight,a=this.cropOffsertX,n=this.cropOffsertY;s.onload=()=>{if(this.cropW!==0){let f=1;this.high&!this.full&&(f=window.devicePixelRatio),this.enlarge!==1&!this.full&&(f=Math.abs(Number(this.enlarge)));let m=this.cropW*f,x=this.cropH*f,u=o*this.scale*f,g=h*this.scale*f,v=(this.x-a+this.trueWidth*(1-this.scale)/2)*f,w=(this.y-n+this.trueHeight*(1-this.scale)/2)*f;switch(d(m,x),i.save(),r){case 0:this.full?(d(m/this.scale,x/this.scale),i.drawImage(s,v/this.scale,w/this.scale,u/this.scale,g/this.scale)):i.drawImage(s,v,w,u,g);break;case 1:case-3:this.full?(d(m/this.scale,x/this.scale),v=v/this.scale+(u/this.scale-g/this.scale)/2,w=w/this.scale+(g/this.scale-u/this.scale)/2,i.rotate(r*90*Math.PI/180),i.drawImage(s,w,-v-g/this.scale,u/this.scale,g/this.scale)):(v=v+(u-g)/2,w=w+(g-u)/2,i.rotate(r*90*Math.PI/180),i.drawImage(s,w,-v-g,u,g));break;case 2:case-2:this.full?(d(m/this.scale,x/this.scale),i.rotate(r*90*Math.PI/180),v=v/this.scale,w=w/this.scale,i.drawImage(s,-v-u/this.scale,-w-g/this.scale,u/this.scale,g/this.scale)):(i.rotate(r*90*Math.PI/180),i.drawImage(s,-v-u,-w-g,u,g));break;case 3:case-1:this.full?(d(m/this.scale,x/this.scale),v=v/this.scale+(u/this.scale-g/this.scale)/2,w=w/this.scale+(g/this.scale-u/this.scale)/2,i.rotate(r*90*Math.PI/180),i.drawImage(s,-w-u/this.scale,v,u/this.scale,g/this.scale)):(v=v+(u-g)/2,w=w+(g-u)/2,i.rotate(r*90*Math.PI/180),i.drawImage(s,-w-u,v,u,g));break;default:this.full?(d(m/this.scale,x/this.scale),i.drawImage(s,v/this.scale,w/this.scale,u/this.scale,g/this.scale)):i.drawImage(s,v,w,u,g)}i.restore()}else{let f=o*this.scale,m=h*this.scale;switch(i.save(),r){case 0:d(f,m),i.drawImage(s,0,0,f,m);break;case 1:case-3:d(m,f),i.rotate(r*90*Math.PI/180),i.drawImage(s,0,-m,f,m);break;case 2:case-2:d(f,m),i.rotate(r*90*Math.PI/180),i.drawImage(s,-f,-m,f,m);break;case 3:case-1:d(m,f),i.rotate(r*90*Math.PI/180),i.drawImage(s,-f,0,f,m);break;default:d(f,m),i.drawImage(s,0,0,f,m)}i.restore()}t(e)};var c=this.img.substr(0,4);c!=="data"&&(s.crossOrigin="Anonymous"),s.src=this.imgs;const p=this.fillColor;function d(f,m){e.width=Math.round(f),e.height=Math.round(m),p&&(i.fillStyle=p,i.fillRect(0,0,e.width,e.height))}},getCropData(t){this.getCropChecked(e=>{t(e.toDataURL("image/"+this.outputType,this.outputSize))})},getCropBlob(t){this.getCropChecked(e=>{e.toBlob(i=>t(i),"image/"+this.outputType,this.outputSize)})},showPreview(){if(this.isCanShow)this.isCanShow=!1,setTimeout(()=>{this.isCanShow=!0},16);else return!1;let t=this.cropW,e=this.cropH,i=this.scale;var s={};s.div={width:`${t}px`,height:`${e}px`};let r=(this.x-this.cropOffsertX)/i,o=(this.y-this.cropOffsertY)/i,h=0;s.w=t,s.h=e,s.url=this.imgs,s.img={width:`${this.trueWidth}px`,height:`${this.trueHeight}px`,transform:`scale(${i})translate3d(${r}px, ${o}px, ${h}px)rotateZ(${this.rotate*90}deg)`},s.html=` 3 |
4 |
5 | 7 |
8 |
`,this.$emit("real-time",s)},reload(){let t=new Image;t.onload=()=>{this.w=parseFloat(window.getComputedStyle(this.$refs.cropper).width),this.h=parseFloat(window.getComputedStyle(this.$refs.cropper).height),this.trueWidth=t.width,this.trueHeight=t.height,this.original?this.scale=1:this.scale=this.checkedMode(),this.$nextTick(()=>{this.x=-(this.trueWidth-this.trueWidth*this.scale)/2+(this.w-this.trueWidth*this.scale)/2,this.y=-(this.trueHeight-this.trueHeight*this.scale)/2+(this.h-this.trueHeight*this.scale)/2,this.loading=!1,this.autoCrop&&this.goAutoCrop(),this.$emit("img-load","success"),setTimeout(()=>{this.showPreview()},20)})},t.onerror=()=>{this.$emit("img-load","error")},t.src=this.imgs},checkedMode(){let t=1,e=this.trueWidth,i=this.trueHeight;const s=this.mode.split(" ");switch(s[0]){case"contain":this.trueWidth>this.w&&(t=this.w/this.trueWidth),this.trueHeight*t>this.h&&(t=this.h/this.trueHeight);break;case"cover":e=this.w,t=e/this.trueWidth,i=i*t,i0;let a=(h?this.trueHeight:this.trueWidth)*this.scale,n=(h?this.trueWidth:this.trueHeight)*this.scale;i=ai?i:r,o=o>s?s:o,this.fixed&&(o=r/this.fixedNumber[0]*this.fixedNumber[1]),o>this.h&&(o=this.h,r=o/this.fixedNumber[1]*this.fixedNumber[0]),this.changeCrop(r,o)},changeCrop(t,e){if(this.centerBox){let i=this.getImgAxis();t>i.x2-i.x1&&(t=i.x2-i.x1,e=t/this.fixedNumber[0]*this.fixedNumber[1]),e>i.y2-i.y1&&(e=i.y2-i.y1,t=e/this.fixedNumber[1]*this.fixedNumber[0])}this.cropW=t,this.cropH=e,this.checkCropLimitSize(),this.$nextTick(()=>{this.cropOffsertX=(this.w-this.cropW)/2,this.cropOffsertY=(this.h-this.cropH)/2,this.centerBox&&this.moveCrop(null,!0)})},refresh(){this.img,this.imgs="",this.scale=1,this.crop=!1,this.rotate=0,this.w=0,this.h=0,this.trueWidth=0,this.trueHeight=0,this.imgIsQqualCrop=!1,this.clearCrop(),this.$nextTick(()=>{this.checkedImg()})},rotateLeft(){this.rotate=this.rotate<=-3?0:this.rotate-1},rotateRight(){this.rotate=this.rotate>=3?0:this.rotate+1},rotateClear(){this.rotate=0},checkoutImgAxis(t,e,i){t=t||this.x,e=e||this.y,i=i||this.scale;let s=!0;if(this.centerBox){let r=this.getImgAxis(t,e,i),o=this.getCropAxis();r.x1>=o.x1&&(s=!1),r.x2<=o.x2&&(s=!1),r.y1>=o.y1&&(s=!1),r.y2<=o.y2&&(s=!1),s||this.changeImgScale(r,o,i)}return s},changeImgScale(t,e,i){let s=this.trueWidth,r=this.trueHeight,o=s*i,h=r*i;if(o>=this.cropW&&h>=this.cropH)this.scale=i;else{const a=this.cropW/s,n=this.cropH/r,c=this.cropH<=r*a?a:n;this.scale=c,o=s*c,h=r*c}this.imgIsQqualCrop||(t.x1>=e.x1&&(this.isRotateRightOrLeft?this.x=e.x1-(s-o)/2-(o-h)/2:this.x=e.x1-(s-o)/2),t.x2<=e.x2&&(this.isRotateRightOrLeft?this.x=e.x1-(s-o)/2-(o-h)/2-h+this.cropW:this.x=e.x2-(s-o)/2-o),t.y1>=e.y1&&(this.isRotateRightOrLeft?this.y=e.y1-(r-h)/2-(h-o)/2:this.y=e.y1-(r-h)/2),t.y2<=e.y2&&(this.isRotateRightOrLeft?this.y=e.y2-(r-h)/2-(h-o)/2-o:this.y=e.y2-(r-h)/2-h)),(ot.scaleImg&&t.scaleImg(...h)),onMouseout:e[29]||(e[29]=(...h)=>t.cancelScale&&t.cancelScale(...h))},[t.imgs?(l.openBlock(),l.createElementBlock("div",A,[l.withDirectives(l.createElementVNode("div",{class:"cropper-box-canvas",style:l.normalizeStyle({width:t.trueWidth+"px",height:t.trueHeight+"px",transform:"scale("+t.scale+","+t.scale+") translate3d("+t.x/t.scale+"px,"+t.y/t.scale+"px,0)rotateZ("+t.rotate*90+"deg)"})},[l.createElementVNode("img",{src:t.imgs,alt:"cropper-img",ref:"cropperImg"},null,8,k)],4),[[l.vShow,!t.loading]])])):l.createCommentVNode("",!0),l.createElementVNode("div",{class:l.normalizeClass(["cropper-drag-box",{"cropper-move":t.move&&!t.crop,"cropper-crop":t.crop,"cropper-modal":t.cropping}]),onMousedown:e[0]||(e[0]=(...h)=>t.startMove&&t.startMove(...h)),onTouchstart:e[1]||(e[1]=(...h)=>t.startMove&&t.startMove(...h))},null,34),l.withDirectives(l.createElementVNode("div",{class:"cropper-crop-box",style:l.normalizeStyle({width:t.cropW+"px",height:t.cropH+"px",transform:"translate3d("+t.cropOffsertX+"px,"+t.cropOffsertY+"px,0)"})},[l.createElementVNode("span",L,[l.createElementVNode("img",{style:l.normalizeStyle({width:t.trueWidth+"px",height:t.trueHeight+"px",transform:"scale("+t.scale+","+t.scale+") translate3d("+(t.x-t.cropOffsertX)/t.scale+"px,"+(t.y-t.cropOffsertY)/t.scale+"px,0)rotateZ("+t.rotate*90+"deg)"}),src:t.imgs,alt:"cropper-img"},null,12,N)]),l.createElementVNode("span",{class:"cropper-face cropper-move",onMousedown:e[2]||(e[2]=(...h)=>t.cropMove&&t.cropMove(...h)),onTouchstart:e[3]||(e[3]=(...h)=>t.cropMove&&t.cropMove(...h))},null,32),t.info?(l.openBlock(),l.createElementBlock("span",{key:0,class:"crop-info",style:l.normalizeStyle({top:t.cropInfo.top})},l.toDisplayString(t.cropInfo.width)+" × "+l.toDisplayString(t.cropInfo.height),5)):l.createCommentVNode("",!0),t.fixedBox?l.createCommentVNode("",!0):(l.openBlock(),l.createElementBlock("span",z,[l.createElementVNode("span",{class:"crop-line line-w",onMousedown:e[4]||(e[4]=h=>t.changeCropSize(h,!1,!0,0,1)),onTouchstart:e[5]||(e[5]=h=>t.changeCropSize(h,!1,!0,0,1))},null,32),l.createElementVNode("span",{class:"crop-line line-a",onMousedown:e[6]||(e[6]=h=>t.changeCropSize(h,!0,!1,1,0)),onTouchstart:e[7]||(e[7]=h=>t.changeCropSize(h,!0,!1,1,0))},null,32),l.createElementVNode("span",{class:"crop-line line-s",onMousedown:e[8]||(e[8]=h=>t.changeCropSize(h,!1,!0,0,2)),onTouchstart:e[9]||(e[9]=h=>t.changeCropSize(h,!1,!0,0,2))},null,32),l.createElementVNode("span",{class:"crop-line line-d",onMousedown:e[10]||(e[10]=h=>t.changeCropSize(h,!0,!1,2,0)),onTouchstart:e[11]||(e[11]=h=>t.changeCropSize(h,!0,!1,2,0))},null,32),l.createElementVNode("span",{class:"crop-point point1",onMousedown:e[12]||(e[12]=h=>t.changeCropSize(h,!0,!0,1,1)),onTouchstart:e[13]||(e[13]=h=>t.changeCropSize(h,!0,!0,1,1))},null,32),l.createElementVNode("span",{class:"crop-point point2",onMousedown:e[14]||(e[14]=h=>t.changeCropSize(h,!1,!0,0,1)),onTouchstart:e[15]||(e[15]=h=>t.changeCropSize(h,!1,!0,0,1))},null,32),l.createElementVNode("span",{class:"crop-point point3",onMousedown:e[16]||(e[16]=h=>t.changeCropSize(h,!0,!0,2,1)),onTouchstart:e[17]||(e[17]=h=>t.changeCropSize(h,!0,!0,2,1))},null,32),l.createElementVNode("span",{class:"crop-point point4",onMousedown:e[18]||(e[18]=h=>t.changeCropSize(h,!0,!1,1,0)),onTouchstart:e[19]||(e[19]=h=>t.changeCropSize(h,!0,!1,1,0))},null,32),l.createElementVNode("span",{class:"crop-point point5",onMousedown:e[20]||(e[20]=h=>t.changeCropSize(h,!0,!1,2,0)),onTouchstart:e[21]||(e[21]=h=>t.changeCropSize(h,!0,!1,2,0))},null,32),l.createElementVNode("span",{class:"crop-point point6",onMousedown:e[22]||(e[22]=h=>t.changeCropSize(h,!0,!0,1,2)),onTouchstart:e[23]||(e[23]=h=>t.changeCropSize(h,!0,!0,1,2))},null,32),l.createElementVNode("span",{class:"crop-point point7",onMousedown:e[24]||(e[24]=h=>t.changeCropSize(h,!1,!0,0,2)),onTouchstart:e[25]||(e[25]=h=>t.changeCropSize(h,!1,!0,0,2))},null,32),l.createElementVNode("span",{class:"crop-point point8",onMousedown:e[26]||(e[26]=h=>t.changeCropSize(h,!0,!0,2,2)),onTouchstart:e[27]||(e[27]=h=>t.changeCropSize(h,!0,!0,2,2))},null,32)]))],4),[[l.vShow,t.cropping]])],544)}const b=E(I,[["render",T],["__scopeId","data-v-a742df44"]]),H={version:"1.1.4",install:function(t){t.component("VueCropper",b)},VueCropper:b};C.VueCropper=b,C.default=H,C.globalCropper=H,Object.defineProperties(C,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}); 9 | -------------------------------------------------------------------------------- /next/dist/vue-cropper.es.js: -------------------------------------------------------------------------------- 1 | import { defineComponent as S, openBlock as y, createElementBlock as x, withDirectives as H, createElementVNode as w, normalizeStyle as b, vShow as W, createCommentVNode as O, normalizeClass as I, toDisplayString as X } from "vue"; 2 | const Y = {}; 3 | Y.getData = (t) => new Promise((e, i) => { 4 | let s = {}; 5 | L(t).then((r) => { 6 | s.arrayBuffer = r; 7 | try { 8 | s.orientation = N(r); 9 | } catch { 10 | s.orientation = -1; 11 | } 12 | e(s); 13 | }).catch((r) => { 14 | i(r); 15 | }); 16 | }); 17 | function L(t) { 18 | let e = null; 19 | return new Promise((i, s) => { 20 | if (t.src) 21 | if (/^data\:/i.test(t.src)) 22 | e = k(t.src), i(e); 23 | else if (/^blob\:/i.test(t.src)) { 24 | var r = new FileReader(); 25 | r.onload = function(h) { 26 | e = h.target.result, i(e); 27 | }, E(t.src, function(h) { 28 | r.readAsArrayBuffer(h); 29 | }); 30 | } else { 31 | var o = new XMLHttpRequest(); 32 | o.onload = function() { 33 | if (this.status == 200 || this.status === 0) 34 | e = o.response, i(e); 35 | else 36 | throw "Could not load image"; 37 | o = null; 38 | }, o.open("GET", t.src, !0), o.responseType = "arraybuffer", o.send(null); 39 | } 40 | else 41 | s("img error"); 42 | }); 43 | } 44 | function E(t, e) { 45 | var i = new XMLHttpRequest(); 46 | i.open("GET", t, !0), i.responseType = "blob", i.onload = function(s) { 47 | (this.status == 200 || this.status === 0) && e(this.response); 48 | }, i.send(); 49 | } 50 | function k(t, e) { 51 | e = e || t.match(/^data\:([^\;]+)\;base64,/mi)[1] || "", t = t.replace(/^data\:([^\;]+)\;base64,/gmi, ""); 52 | for (var i = atob(t), s = i.length % 2 == 0 ? i.length : i.length + 1, r = new ArrayBuffer(s), o = new Uint16Array(r), h = 0; h < s; h++) 53 | o[h] = i.charCodeAt(h); 54 | return r; 55 | } 56 | function T(t, e, i) { 57 | var s = "", r; 58 | for (r = e, i += e; r < i; r++) 59 | s += String.fromCharCode(t.getUint8(r)); 60 | return s; 61 | } 62 | function N(t) { 63 | var e = new DataView(t), i = e.byteLength, s, r, o, h, a, n, c, l, f, p; 64 | if (e.getUint8(0) === 255 && e.getUint8(1) === 216) 65 | for (f = 2; f < i; ) { 66 | if (e.getUint8(f) === 255 && e.getUint8(f + 1) === 225) { 67 | c = f; 68 | break; 69 | } 70 | f++; 71 | } 72 | if (c && (r = c + 4, o = c + 10, T(e, r, 4) === "Exif" && (n = e.getUint16(o), a = n === 18761, (a || n === 19789) && e.getUint16(o + 2, a) === 42 && (h = e.getUint32(o + 4, a), h >= 8 && (l = o + h)))), l) { 73 | for (i = e.getUint16(l, a), p = 0; p < i; p++) 74 | if (f = l + p * 12 + 2, e.getUint16(f, a) === 274) { 75 | f += 8, s = e.getUint16(f, a); 76 | break; 77 | } 78 | } 79 | return s; 80 | } 81 | const $ = (t, e) => { 82 | const i = t.__vccOpts || t; 83 | for (const [s, r] of e) 84 | i[s] = r; 85 | return i; 86 | }, z = S({ 87 | data: function() { 88 | return { 89 | // 容器高宽 90 | w: 0, 91 | h: 0, 92 | // 图片缩放比例 93 | scale: 1, 94 | // 图片偏移x轴 95 | x: 0, 96 | // 图片偏移y轴 97 | y: 0, 98 | // 图片加载 99 | loading: !0, 100 | // 图片真实宽度 101 | trueWidth: 0, 102 | // 图片真实高度 103 | trueHeight: 0, 104 | move: !0, 105 | // 移动的x 106 | moveX: 0, 107 | // 移动的y 108 | moveY: 0, 109 | // 开启截图 110 | crop: !1, 111 | // 正在截图 112 | cropping: !1, 113 | // 裁剪框大小 114 | cropW: 0, 115 | cropH: 0, 116 | cropOldW: 0, 117 | cropOldH: 0, 118 | // 判断是否能够改变 119 | canChangeX: !1, 120 | canChangeY: !1, 121 | // 改变的基准点 122 | changeCropTypeX: 1, 123 | changeCropTypeY: 1, 124 | // 裁剪框的坐标轴 125 | cropX: 0, 126 | cropY: 0, 127 | cropChangeX: 0, 128 | cropChangeY: 0, 129 | cropOffsertX: 0, 130 | cropOffsertY: 0, 131 | // 支持的滚动事件 132 | support: "", 133 | // 移动端手指缩放 134 | touches: [], 135 | touchNow: !1, 136 | // 图片旋转 137 | rotate: 0, 138 | isIos: !1, 139 | orientation: 0, 140 | imgs: "", 141 | // 图片缩放系数 142 | coe: 0.2, 143 | // 是否正在多次缩放 144 | scaling: !1, 145 | scalingSet: "", 146 | coeStatus: "", 147 | // 控制emit触发频率 148 | isCanShow: !0, 149 | // 图片是否等于截图大小 150 | imgIsQqualCrop: !1 151 | }; 152 | }, 153 | props: { 154 | img: { 155 | type: [String, Blob, null, File], 156 | default: "" 157 | }, 158 | // 输出图片压缩比 159 | outputSize: { 160 | type: Number, 161 | default: 1 162 | }, 163 | outputType: { 164 | type: String, 165 | default: "jpeg" 166 | }, 167 | info: { 168 | type: Boolean, 169 | default: !0 170 | }, 171 | // 是否开启滚轮放大缩小 172 | canScale: { 173 | type: Boolean, 174 | default: !0 175 | }, 176 | // 是否自成截图框 177 | autoCrop: { 178 | type: Boolean, 179 | default: !1 180 | }, 181 | autoCropWidth: { 182 | type: [Number, String], 183 | default: 0 184 | }, 185 | autoCropHeight: { 186 | type: [Number, String], 187 | default: 0 188 | }, 189 | // 是否开启固定宽高比 190 | fixed: { 191 | type: Boolean, 192 | default: !1 193 | }, 194 | // 宽高比 w/h 195 | fixedNumber: { 196 | type: Array, 197 | default: () => [1, 1] 198 | }, 199 | // 固定大小 禁止改变截图框大小 200 | fixedBox: { 201 | type: Boolean, 202 | default: !1 203 | }, 204 | // 输出截图是否缩放 205 | full: { 206 | type: Boolean, 207 | default: !1 208 | }, 209 | // 是否可以拖动图片 210 | canMove: { 211 | type: Boolean, 212 | default: !0 213 | }, 214 | // 是否可以拖动截图框 215 | canMoveBox: { 216 | type: Boolean, 217 | default: !0 218 | }, 219 | // 上传图片按照原始比例显示 220 | original: { 221 | type: Boolean, 222 | default: !1 223 | }, 224 | // 截图框能否超过图片 225 | centerBox: { 226 | type: Boolean, 227 | default: !1 228 | }, 229 | // 是否根据dpr输出高清图片 230 | high: { 231 | type: Boolean, 232 | default: !0 233 | }, 234 | // 截图框展示宽高类型 235 | infoTrue: { 236 | type: Boolean, 237 | default: !1 238 | }, 239 | // 可以压缩图片宽高 默认不超过200 240 | maxImgSize: { 241 | type: [Number, String], 242 | default: 2e3 243 | }, 244 | // 倍数 可渲染当前截图框的n倍 0 - 1000; 245 | enlarge: { 246 | type: [Number, String], 247 | default: 1 248 | }, 249 | // 自动预览的固定宽度 250 | preW: { 251 | type: [Number, String], 252 | default: 0 253 | }, 254 | /* 255 | 图片布局方式 mode 实现和css背景一样的效果 256 | contain 居中布局 默认不会缩放 保证图片在容器里面 mode: 'contain' 257 | cover 拉伸布局 填充整个容器 mode: 'cover' 258 | 如果仅有一个数值被给定,这个数值将作为宽度值大小,高度值将被设定为auto。 mode: '50px' 259 | 如果有两个数值被给定,第一个将作为宽度值大小,第二个作为高度值大小。 mode: '50px 60px' 260 | */ 261 | mode: { 262 | type: String, 263 | default: "contain" 264 | }, 265 | //限制最小区域,可传1以上的数字和字符串,限制长宽都是这么大 266 | // 也可以传数组[90,90] 267 | limitMinSize: { 268 | type: [Number, Array, String], 269 | default: () => 10, 270 | validator: function(t) { 271 | return Array.isArray(t) ? Number(t[0]) >= 0 && Number(t[1]) >= 0 : Number(t) >= 0; 272 | } 273 | }, 274 | // 导出时,填充背景颜色 275 | fillColor: { 276 | type: String, 277 | default: "" 278 | } 279 | }, 280 | computed: { 281 | cropInfo() { 282 | let t = {}; 283 | if (t.top = this.cropOffsertY > 21 ? "-21px" : "0px", t.width = this.cropW > 0 ? this.cropW : 0, t.height = this.cropH > 0 ? this.cropH : 0, this.infoTrue) { 284 | let e = 1; 285 | this.high && !this.full && (e = window.devicePixelRatio), this.enlarge !== 1 & !this.full && (e = Math.abs(Number(this.enlarge))), t.width = t.width * e, t.height = t.height * e, this.full && (t.width = t.width / this.scale, t.height = t.height / this.scale); 286 | } 287 | return t.width = t.width.toFixed(0), t.height = t.height.toFixed(0), t; 288 | }, 289 | isIE() { 290 | return !!window.ActiveXObject || "ActiveXObject" in window; 291 | }, 292 | passive() { 293 | return this.isIE ? null : { 294 | passive: !1 295 | }; 296 | }, 297 | // 是否处于左右旋转 298 | isRotateRightOrLeft() { 299 | return [1, -1, 3, -3].includes(this.rotate); 300 | } 301 | }, 302 | watch: { 303 | // 如果图片改变, 重新布局 304 | img() { 305 | this.checkedImg(); 306 | }, 307 | imgs(t) { 308 | t !== "" && this.reload(); 309 | }, 310 | cropW() { 311 | this.showPreview(); 312 | }, 313 | cropH() { 314 | this.showPreview(); 315 | }, 316 | cropOffsertX() { 317 | this.showPreview(); 318 | }, 319 | cropOffsertY() { 320 | this.showPreview(); 321 | }, 322 | scale(t, e) { 323 | this.showPreview(); 324 | }, 325 | x() { 326 | this.showPreview(); 327 | }, 328 | y() { 329 | this.showPreview(); 330 | }, 331 | autoCrop(t) { 332 | t && this.goAutoCrop(); 333 | }, 334 | // 修改了自动截图框 335 | autoCropWidth() { 336 | this.autoCrop && this.goAutoCrop(); 337 | }, 338 | autoCropHeight() { 339 | this.autoCrop && this.goAutoCrop(); 340 | }, 341 | mode() { 342 | this.checkedImg(); 343 | }, 344 | rotate() { 345 | this.showPreview(), this.autoCrop ? this.goAutoCrop(this.cropW, this.cropH) : (this.cropW > 0 || this.cropH > 0) && this.goAutoCrop(this.cropW, this.cropH); 346 | } 347 | }, 348 | methods: { 349 | getVersion(t) { 350 | var e = navigator.userAgent.split(" "), i = ""; 351 | let s = 0; 352 | const r = new RegExp(t, "i"); 353 | for (var o = 0; o < e.length; o++) 354 | r.test(e[o]) && (i = e[o]); 355 | return i ? s = i.split("/")[1].split(".") : s = ["0", "0", "0"], s; 356 | }, 357 | checkOrientationImage(t, e, i, s) { 358 | if (this.getVersion("chrome")[0] >= 81) 359 | e = -1; 360 | else if (this.getVersion("safari")[0] >= 605) { 361 | const h = this.getVersion("version"); 362 | h[0] > 13 && h[1] > 1 && (e = -1); 363 | } else { 364 | const h = navigator.userAgent.toLowerCase().match(/cpu iphone os (.*?) like mac os/); 365 | if (h) { 366 | let a = h[1]; 367 | a = a.split("_"), (a[0] > 13 || a[0] >= 13 && a[1] >= 4) && (e = -1); 368 | } 369 | } 370 | let r = document.createElement("canvas"), o = r.getContext("2d"); 371 | switch (o.save(), e) { 372 | case 2: 373 | r.width = i, r.height = s, o.translate(i, 0), o.scale(-1, 1); 374 | break; 375 | case 3: 376 | r.width = i, r.height = s, o.translate(i / 2, s / 2), o.rotate(180 * Math.PI / 180), o.translate(-i / 2, -s / 2); 377 | break; 378 | case 4: 379 | r.width = i, r.height = s, o.translate(0, s), o.scale(1, -1); 380 | break; 381 | case 5: 382 | r.height = i, r.width = s, o.rotate(0.5 * Math.PI), o.scale(1, -1); 383 | break; 384 | case 6: 385 | r.width = s, r.height = i, o.translate(s / 2, i / 2), o.rotate(90 * Math.PI / 180), o.translate(-i / 2, -s / 2); 386 | break; 387 | case 7: 388 | r.height = i, r.width = s, o.rotate(0.5 * Math.PI), o.translate(i, -s), o.scale(-1, 1); 389 | break; 390 | case 8: 391 | r.height = i, r.width = s, o.translate(s / 2, i / 2), o.rotate(-90 * Math.PI / 180), o.translate(-i / 2, -s / 2); 392 | break; 393 | default: 394 | r.width = i, r.height = s; 395 | } 396 | o.drawImage(t, 0, 0, i, s), o.restore(), r.toBlob( 397 | (h) => { 398 | let a = URL.createObjectURL(h); 399 | URL.revokeObjectURL(this.imgs), this.imgs = a; 400 | }, 401 | "image/" + this.outputType, 402 | 1 403 | ); 404 | }, 405 | // checkout img 406 | checkedImg() { 407 | if (this.img === null || this.img === "") { 408 | this.imgs = "", this.clearCrop(); 409 | return; 410 | } 411 | this.loading = !0, this.scale = 1, this.rotate = 0, this.imgIsQqualCrop = !1, this.clearCrop(); 412 | let t = new Image(); 413 | if (t.onload = () => { 414 | if (this.img === "") 415 | return this.$emit("img-load", new Error("图片不能为空")), !1; 416 | let i = t.width, s = t.height; 417 | Y.getData(t).then((r) => { 418 | this.orientation = r.orientation || 1; 419 | let o = Number(this.maxImgSize); 420 | if (!this.orientation && i < o & s < o) { 421 | this.imgs = this.img; 422 | return; 423 | } 424 | i > o && (s = s / i * o, i = o), s > o && (i = i / s * o, s = o), this.checkOrientationImage(t, this.orientation, i, s); 425 | }).catch((r) => { 426 | this.$emit("img-load", "error"), this.$emit("img-load-error", r); 427 | }); 428 | }, t.onerror = (i) => { 429 | this.$emit("img-load", "error"), this.$emit("img-load-error", i); 430 | }, this.img.substr(0, 4) !== "data" && (t.crossOrigin = ""), this.isIE) { 431 | var e = new XMLHttpRequest(); 432 | e.onload = function() { 433 | var i = URL.createObjectURL(this.response); 434 | t.src = i; 435 | }, e.open("GET", this.img, !0), e.responseType = "blob", e.send(); 436 | } else 437 | t.src = this.img; 438 | }, 439 | // 当按下鼠标键 440 | startMove(t) { 441 | if (t.preventDefault(), this.move && !this.crop) { 442 | if (!this.canMove) 443 | return !1; 444 | this.moveX = ("clientX" in t ? t.clientX : t.touches[0].clientX) - this.x, this.moveY = ("clientY" in t ? t.clientY : t.touches[0].clientY) - this.y, t.touches ? (window.addEventListener("touchmove", this.moveImg), window.addEventListener("touchend", this.leaveImg), t.touches.length == 2 && (this.touches = t.touches, window.addEventListener("touchmove", this.touchScale), window.addEventListener("touchend", this.cancelTouchScale))) : (window.addEventListener("mousemove", this.moveImg), window.addEventListener("mouseup", this.leaveImg)), this.$emit("img-moving", { 445 | moving: !0, 446 | axis: this.getImgAxis() 447 | }); 448 | } else 449 | this.cropping = !0, window.addEventListener("mousemove", this.createCrop), window.addEventListener("mouseup", this.endCrop), window.addEventListener("touchmove", this.createCrop), window.addEventListener("touchend", this.endCrop), this.cropOffsertX = t.offsetX ? t.offsetX : t.touches[0].pageX - this.$refs.cropper.offsetLeft, this.cropOffsertY = t.offsetY ? t.offsetY : t.touches[0].pageY - this.$refs.cropper.offsetTop, this.cropX = "clientX" in t ? t.clientX : t.touches[0].clientX, this.cropY = "clientY" in t ? t.clientY : t.touches[0].clientY, this.cropChangeX = this.cropOffsertX, this.cropChangeY = this.cropOffsertY, this.cropW = 0, this.cropH = 0; 450 | }, 451 | // 移动端缩放 452 | touchScale(t) { 453 | t.preventDefault(); 454 | let e = this.scale; 455 | var i = { 456 | x: this.touches[0].clientX, 457 | y: this.touches[0].clientY 458 | }, s = { 459 | x: t.touches[0].clientX, 460 | y: t.touches[0].clientY 461 | }, r = { 462 | x: this.touches[1].clientX, 463 | y: this.touches[1].clientY 464 | }, o = { 465 | x: t.touches[1].clientX, 466 | y: t.touches[1].clientY 467 | }, h = Math.sqrt( 468 | Math.pow(i.x - r.x, 2) + Math.pow(i.y - r.y, 2) 469 | ), a = Math.sqrt( 470 | Math.pow(s.x - o.x, 2) + Math.pow(s.y - o.y, 2) 471 | ), n = a - h, c = 1; 472 | c = c / this.trueWidth > c / this.trueHeight ? c / this.trueHeight : c / this.trueWidth, c = c > 0.1 ? 0.1 : c; 473 | var l = c * n; 474 | if (!this.touchNow) { 475 | if (this.touchNow = !0, n > 0 ? e += Math.abs(l) : n < 0 && e > Math.abs(l) && (e -= Math.abs(l)), this.touches = t.touches, setTimeout(() => { 476 | this.touchNow = !1; 477 | }, 8), !this.checkoutImgAxis(this.x, this.y, e)) 478 | return !1; 479 | this.scale = e; 480 | } 481 | }, 482 | cancelTouchScale(t) { 483 | window.removeEventListener("touchmove", this.touchScale); 484 | }, 485 | // 移动图片 486 | moveImg(t) { 487 | if (t.preventDefault(), t.touches && t.touches.length === 2) 488 | return this.touches = t.touches, window.addEventListener("touchmove", this.touchScale), window.addEventListener("touchend", this.cancelTouchScale), window.removeEventListener("touchmove", this.moveImg), !1; 489 | let e = "clientX" in t ? t.clientX : t.touches[0].clientX, i = "clientY" in t ? t.clientY : t.touches[0].clientY, s, r; 490 | s = e - this.moveX, r = i - this.moveY, this.$nextTick(() => { 491 | if (this.centerBox) { 492 | let o = this.getImgAxis(s, r, this.scale), h = this.getCropAxis(), a = this.trueHeight * this.scale, n = this.trueWidth * this.scale, c, l, f, p; 493 | switch (this.rotate) { 494 | case 1: 495 | case -1: 496 | case 3: 497 | case -3: 498 | c = this.cropOffsertX - this.trueWidth * (1 - this.scale) / 2 + (a - n) / 2, l = this.cropOffsertY - this.trueHeight * (1 - this.scale) / 2 + (n - a) / 2, f = c - a + this.cropW, p = l - n + this.cropH; 499 | break; 500 | default: 501 | c = this.cropOffsertX - this.trueWidth * (1 - this.scale) / 2, l = this.cropOffsertY - this.trueHeight * (1 - this.scale) / 2, f = c - n + this.cropW, p = l - a + this.cropH; 502 | break; 503 | } 504 | o.x1 >= h.x1 && (s = c), o.y1 >= h.y1 && (r = l), o.x2 <= h.x2 && (s = f), o.y2 <= h.y2 && (r = p); 505 | } 506 | this.x = s, this.y = r, this.$emit("img-moving", { 507 | moving: !0, 508 | axis: this.getImgAxis() 509 | }); 510 | }); 511 | }, 512 | // 移动图片结束 513 | leaveImg(t) { 514 | window.removeEventListener("mousemove", this.moveImg), window.removeEventListener("touchmove", this.moveImg), window.removeEventListener("mouseup", this.leaveImg), window.removeEventListener("touchend", this.leaveImg), this.$emit("img-moving", { 515 | moving: !1, 516 | axis: this.getImgAxis() 517 | }); 518 | }, 519 | // 缩放图片 520 | scaleImg() { 521 | this.canScale && window.addEventListener(this.support, this.changeSize, this.passive); 522 | }, 523 | // 移出框 524 | cancelScale() { 525 | this.canScale && window.removeEventListener(this.support, this.changeSize); 526 | }, 527 | // 改变大小函数 528 | changeSize(t) { 529 | t.preventDefault(); 530 | let e = this.scale; 531 | var i = t.deltaY || t.wheelDelta, s = navigator.userAgent.indexOf("Firefox"); 532 | i = s > 0 ? i * 30 : i, this.isIE && (i = -i); 533 | var r = this.coe; 534 | r = r / this.trueWidth > r / this.trueHeight ? r / this.trueHeight : r / this.trueWidth; 535 | var o = r * i; 536 | o < 0 ? e += Math.abs(o) : e > Math.abs(o) && (e -= Math.abs(o)); 537 | let h = o < 0 ? "add" : "reduce"; 538 | if (h !== this.coeStatus && (this.coeStatus = h, this.coe = 0.2), this.scaling || (this.scalingSet = setTimeout(() => { 539 | this.scaling = !1, this.coe = this.coe += 0.01; 540 | }, 50)), this.scaling = !0, !this.checkoutImgAxis(this.x, this.y, e)) 541 | return !1; 542 | this.scale = e; 543 | }, 544 | // 修改图片大小函数 545 | changeScale(t) { 546 | let e = this.scale; 547 | t = t || 1; 548 | var i = 20; 549 | if (i = i / this.trueWidth > i / this.trueHeight ? i / this.trueHeight : i / this.trueWidth, t = t * i, t > 0 ? e += Math.abs(t) : e > Math.abs(t) && (e -= Math.abs(t)), !this.checkoutImgAxis(this.x, this.y, e)) 550 | return !1; 551 | this.scale = e; 552 | }, 553 | // 创建截图框 554 | createCrop(t) { 555 | t.preventDefault(); 556 | var e = "clientX" in t ? t.clientX : t.touches ? t.touches[0].clientX : 0, i = "clientY" in t ? t.clientY : t.touches ? t.touches[0].clientY : 0; 557 | this.$nextTick(() => { 558 | var s = e - this.cropX, r = i - this.cropY; 559 | if (s > 0 ? (this.cropW = s + this.cropChangeX > this.w ? this.w - this.cropChangeX : s, this.cropOffsertX = this.cropChangeX) : (this.cropW = this.w - this.cropChangeX + Math.abs(s) > this.w ? this.cropChangeX : Math.abs(s), this.cropOffsertX = this.cropChangeX + s > 0 ? this.cropChangeX + s : 0), !this.fixed) 560 | r > 0 ? (this.cropH = r + this.cropChangeY > this.h ? this.h - this.cropChangeY : r, this.cropOffsertY = this.cropChangeY) : (this.cropH = this.h - this.cropChangeY + Math.abs(r) > this.h ? this.cropChangeY : Math.abs(r), this.cropOffsertY = this.cropChangeY + r > 0 ? this.cropChangeY + r : 0); 561 | else { 562 | var o = this.cropW / this.fixedNumber[0] * this.fixedNumber[1]; 563 | o + this.cropOffsertY > this.h ? (this.cropH = this.h - this.cropOffsertY, this.cropW = this.cropH / this.fixedNumber[1] * this.fixedNumber[0], s > 0 ? this.cropOffsertX = this.cropChangeX : this.cropOffsertX = this.cropChangeX - this.cropW) : this.cropH = o, this.cropOffsertY = this.cropOffsertY; 564 | } 565 | }); 566 | }, 567 | // 改变截图框大小 568 | changeCropSize(t, e, i, s, r) { 569 | t.preventDefault(), window.addEventListener("mousemove", this.changeCropNow), window.addEventListener("mouseup", this.changeCropEnd), window.addEventListener("touchmove", this.changeCropNow), window.addEventListener("touchend", this.changeCropEnd), this.canChangeX = e, this.canChangeY = i, this.changeCropTypeX = s, this.changeCropTypeY = r, this.cropX = "clientX" in t ? t.clientX : t.touches[0].clientX, this.cropY = "clientY" in t ? t.clientY : t.touches[0].clientY, this.cropOldW = this.cropW, this.cropOldH = this.cropH, this.cropChangeX = this.cropOffsertX, this.cropChangeY = this.cropOffsertY, this.fixed && this.canChangeX && this.canChangeY && (this.canChangeY = 0), this.$emit("change-crop-size", { 570 | width: this.cropW, 571 | height: this.cropH 572 | }); 573 | }, 574 | // 正在改变 575 | changeCropNow(t) { 576 | t.preventDefault(); 577 | var e = "clientX" in t ? t.clientX : t.touches ? t.touches[0].clientX : 0, i = "clientY" in t ? t.clientY : t.touches ? t.touches[0].clientY : 0; 578 | let s = this.w, r = this.h, o = 0, h = 0; 579 | if (this.centerBox) { 580 | let c = this.getImgAxis(), l = c.x2, f = c.y2; 581 | o = c.x1 > 0 ? c.x1 : 0, h = c.y1 > 0 ? c.y1 : 0, s > l && (s = l), r > f && (r = f); 582 | } 583 | const [a, n] = this.checkCropLimitSize(); 584 | this.$nextTick(() => { 585 | var c = e - this.cropX, l = i - this.cropY; 586 | if (this.canChangeX && (this.changeCropTypeX === 1 ? this.cropOldW - c < a ? (this.cropW = a, this.cropOffsertX = this.cropOldW + this.cropChangeX - o - a) : this.cropOldW - c > 0 ? (this.cropW = s - this.cropChangeX - c <= s - o ? this.cropOldW - c : this.cropOldW + this.cropChangeX - o, this.cropOffsertX = s - this.cropChangeX - c <= s - o ? this.cropChangeX + c : o) : (this.cropW = Math.abs(c) + this.cropChangeX <= s ? Math.abs(c) - this.cropOldW : s - this.cropOldW - this.cropChangeX, this.cropOffsertX = this.cropChangeX + this.cropOldW) : this.changeCropTypeX === 2 && (this.cropOldW + c < a ? this.cropW = a : this.cropOldW + c > 0 ? (this.cropW = this.cropOldW + c + this.cropOffsertX <= s ? this.cropOldW + c : s - this.cropOffsertX, this.cropOffsertX = this.cropChangeX) : (this.cropW = s - this.cropChangeX + Math.abs(c + this.cropOldW) <= s - o ? Math.abs(c + this.cropOldW) : this.cropChangeX - o, this.cropOffsertX = s - this.cropChangeX + Math.abs(c + this.cropOldW) <= s - o ? this.cropChangeX - Math.abs(c + this.cropOldW) : o))), this.canChangeY && (this.changeCropTypeY === 1 ? this.cropOldH - l < n ? (this.cropH = n, this.cropOffsertY = this.cropOldH + this.cropChangeY - h - n) : this.cropOldH - l > 0 ? (this.cropH = r - this.cropChangeY - l <= r - h ? this.cropOldH - l : this.cropOldH + this.cropChangeY - h, this.cropOffsertY = r - this.cropChangeY - l <= r - h ? this.cropChangeY + l : h) : (this.cropH = Math.abs(l) + this.cropChangeY <= r ? Math.abs(l) - this.cropOldH : r - this.cropOldH - this.cropChangeY, this.cropOffsertY = this.cropChangeY + this.cropOldH) : this.changeCropTypeY === 2 && (this.cropOldH + l < n ? this.cropH = n : this.cropOldH + l > 0 ? (this.cropH = this.cropOldH + l + this.cropOffsertY <= r ? this.cropOldH + l : r - this.cropOffsertY, this.cropOffsertY = this.cropChangeY) : (this.cropH = r - this.cropChangeY + Math.abs(l + this.cropOldH) <= r - h ? Math.abs(l + this.cropOldH) : this.cropChangeY - h, this.cropOffsertY = r - this.cropChangeY + Math.abs(l + this.cropOldH) <= r - h ? this.cropChangeY - Math.abs(l + this.cropOldH) : h))), this.canChangeX && this.fixed) { 587 | var f = this.cropW / this.fixedNumber[0] * this.fixedNumber[1]; 588 | f < n ? (this.cropH = n, this.cropW = this.fixedNumber[0] * n / this.fixedNumber[1], this.changeCropTypeX === 1 && (this.cropOffsertX = this.cropChangeX + (this.cropOldW - this.cropW))) : f + this.cropOffsertY > r ? (this.cropH = r - this.cropOffsertY, this.cropW = this.cropH / this.fixedNumber[1] * this.fixedNumber[0], this.changeCropTypeX === 1 && (this.cropOffsertX = this.cropChangeX + (this.cropOldW - this.cropW))) : this.cropH = f; 589 | } 590 | if (this.canChangeY && this.fixed) { 591 | var p = this.cropH / this.fixedNumber[1] * this.fixedNumber[0]; 592 | p < a ? (this.cropW = a, this.cropH = this.fixedNumber[1] * a / this.fixedNumber[0], this.cropOffsertY = this.cropOldH + this.cropChangeY - this.cropH) : p + this.cropOffsertX > s ? (this.cropW = s - this.cropOffsertX, this.cropH = this.cropW / this.fixedNumber[0] * this.fixedNumber[1]) : this.cropW = p; 593 | } 594 | }); 595 | }, 596 | checkCropLimitSize() { 597 | let { cropW: t, cropH: e, limitMinSize: i } = this, s = new Array(); 598 | return Array.isArray(i) ? s = i : s = [i, i], t = parseFloat(s[0]), e = parseFloat(s[1]), [t, e]; 599 | }, 600 | // 结束改变 601 | changeCropEnd(t) { 602 | window.removeEventListener("mousemove", this.changeCropNow), window.removeEventListener("mouseup", this.changeCropEnd), window.removeEventListener("touchmove", this.changeCropNow), window.removeEventListener("touchend", this.changeCropEnd); 603 | }, 604 | // 根据比例x/y,最小宽度,最小高度,现有宽度,现有高度,得到应该有的宽度和高度 605 | calculateSize(t, e, i, s, r, o) { 606 | const h = t / e; 607 | let a = r, n = o; 608 | return a < i && (a = i, n = Math.ceil(a / h)), n < s && (n = s, a = Math.ceil(n * h), a < i && (a = i, n = Math.ceil(a / h))), a < r && (a = r, n = Math.ceil(a / h)), n < o && (n = o, a = Math.ceil(n * h)), { width: a, height: n }; 609 | }, 610 | // 创建完成 611 | endCrop() { 612 | this.cropW === 0 && this.cropH === 0 && (this.cropping = !1); 613 | let [t, e] = this.checkCropLimitSize(); 614 | const { width: i, height: s } = this.fixed ? this.calculateSize( 615 | this.fixedNumber[0], 616 | this.fixedNumber[1], 617 | t, 618 | e, 619 | this.cropW, 620 | this.cropH 621 | ) : { width: t, height: e }; 622 | i > this.cropW && (this.cropW = i, this.cropOffsertX + i > this.w && (this.cropOffsertX = this.w - i)), s > this.cropH && (this.cropH = s, this.cropOffsertY + s > this.h && (this.cropOffsertY = this.h - s)), window.removeEventListener("mousemove", this.createCrop), window.removeEventListener("mouseup", this.endCrop), window.removeEventListener("touchmove", this.createCrop), window.removeEventListener("touchend", this.endCrop); 623 | }, 624 | // 开始截图 625 | startCrop() { 626 | this.crop = !0; 627 | }, 628 | // 停止截图 629 | stopCrop() { 630 | this.crop = !1; 631 | }, 632 | // 清除截图 633 | clearCrop() { 634 | this.cropping = !1, this.cropW = 0, this.cropH = 0; 635 | }, 636 | // 截图移动 637 | cropMove(t) { 638 | if (t.preventDefault(), !this.canMoveBox) 639 | return this.crop = !1, this.startMove(t), !1; 640 | if (t.touches && t.touches.length === 2) 641 | return this.crop = !1, this.startMove(t), this.leaveCrop(), !1; 642 | window.addEventListener("mousemove", this.moveCrop), window.addEventListener("mouseup", this.leaveCrop), window.addEventListener("touchmove", this.moveCrop), window.addEventListener("touchend", this.leaveCrop); 643 | let e = "clientX" in t ? t.clientX : t.touches[0].clientX, i = "clientY" in t ? t.clientY : t.touches[0].clientY, s, r; 644 | s = e - this.cropOffsertX, r = i - this.cropOffsertY, this.cropX = s, this.cropY = r, this.$emit("crop-moving", { 645 | moving: !0, 646 | axis: this.getCropAxis() 647 | }); 648 | }, 649 | moveCrop(t, e) { 650 | let i = 0, s = 0; 651 | t && (t.preventDefault(), i = "clientX" in t ? t.clientX : t.touches[0].clientX, s = "clientY" in t ? t.clientY : t.touches[0].clientY), this.$nextTick(() => { 652 | let r, o, h = i - this.cropX, a = s - this.cropY; 653 | if (e && (h = this.cropOffsertX, a = this.cropOffsertY), h <= 0 ? r = 0 : h + this.cropW > this.w ? r = this.w - this.cropW : r = h, a <= 0 ? o = 0 : a + this.cropH > this.h ? o = this.h - this.cropH : o = a, this.centerBox) { 654 | let n = this.getImgAxis(); 655 | r <= n.x1 && (r = n.x1), r + this.cropW > n.x2 && (r = n.x2 - this.cropW), o <= n.y1 && (o = n.y1), o + this.cropH > n.y2 && (o = n.y2 - this.cropH); 656 | } 657 | this.cropOffsertX = r, this.cropOffsertY = o, this.$emit("crop-moving", { 658 | moving: !0, 659 | axis: this.getCropAxis() 660 | }); 661 | }); 662 | }, 663 | // 算出不同场景下面 图片相对于外层容器的坐标轴 664 | getImgAxis(t, e, i) { 665 | t = t || this.x, e = e || this.y, i = i || this.scale; 666 | let s = { 667 | x1: 0, 668 | x2: 0, 669 | y1: 0, 670 | y2: 0 671 | }, r = this.trueWidth * i, o = this.trueHeight * i; 672 | switch (this.rotate) { 673 | case 0: 674 | s.x1 = t + this.trueWidth * (1 - i) / 2, s.x2 = s.x1 + this.trueWidth * i, s.y1 = e + this.trueHeight * (1 - i) / 2, s.y2 = s.y1 + this.trueHeight * i; 675 | break; 676 | case 1: 677 | case -1: 678 | case 3: 679 | case -3: 680 | s.x1 = t + this.trueWidth * (1 - i) / 2 + (r - o) / 2, s.x2 = s.x1 + this.trueHeight * i, s.y1 = e + this.trueHeight * (1 - i) / 2 + (o - r) / 2, s.y2 = s.y1 + this.trueWidth * i; 681 | break; 682 | default: 683 | s.x1 = t + this.trueWidth * (1 - i) / 2, s.x2 = s.x1 + this.trueWidth * i, s.y1 = e + this.trueHeight * (1 - i) / 2, s.y2 = s.y1 + this.trueHeight * i; 684 | break; 685 | } 686 | return s; 687 | }, 688 | // 获取截图框的坐标轴 689 | getCropAxis() { 690 | let t = { 691 | x1: 0, 692 | x2: 0, 693 | y1: 0, 694 | y2: 0 695 | }; 696 | return t.x1 = this.cropOffsertX, t.x2 = t.x1 + this.cropW, t.y1 = this.cropOffsertY, t.y2 = t.y1 + this.cropH, t; 697 | }, 698 | leaveCrop(t) { 699 | window.removeEventListener("mousemove", this.moveCrop), window.removeEventListener("mouseup", this.leaveCrop), window.removeEventListener("touchmove", this.moveCrop), window.removeEventListener("touchend", this.leaveCrop), this.$emit("crop-moving", { 700 | moving: !1, 701 | axis: this.getCropAxis() 702 | }); 703 | }, 704 | getCropChecked(t) { 705 | let e = document.createElement("canvas"), i = e.getContext("2d"), s = new Image(), r = this.rotate, o = this.trueWidth, h = this.trueHeight, a = this.cropOffsertX, n = this.cropOffsertY; 706 | s.onload = () => { 707 | if (this.cropW !== 0) { 708 | let p = 1; 709 | this.high & !this.full && (p = window.devicePixelRatio), this.enlarge !== 1 & !this.full && (p = Math.abs(Number(this.enlarge))); 710 | let d = this.cropW * p, C = this.cropH * p, u = o * this.scale * p, g = h * this.scale * p, m = (this.x - a + this.trueWidth * (1 - this.scale) / 2) * p, v = (this.y - n + this.trueHeight * (1 - this.scale) / 2) * p; 711 | switch (f(d, C), i.save(), r) { 712 | case 0: 713 | this.full ? (f(d / this.scale, C / this.scale), i.drawImage( 714 | s, 715 | m / this.scale, 716 | v / this.scale, 717 | u / this.scale, 718 | g / this.scale 719 | )) : i.drawImage(s, m, v, u, g); 720 | break; 721 | case 1: 722 | case -3: 723 | this.full ? (f(d / this.scale, C / this.scale), m = m / this.scale + (u / this.scale - g / this.scale) / 2, v = v / this.scale + (g / this.scale - u / this.scale) / 2, i.rotate(r * 90 * Math.PI / 180), i.drawImage( 724 | s, 725 | v, 726 | -m - g / this.scale, 727 | u / this.scale, 728 | g / this.scale 729 | )) : (m = m + (u - g) / 2, v = v + (g - u) / 2, i.rotate(r * 90 * Math.PI / 180), i.drawImage(s, v, -m - g, u, g)); 730 | break; 731 | case 2: 732 | case -2: 733 | this.full ? (f(d / this.scale, C / this.scale), i.rotate(r * 90 * Math.PI / 180), m = m / this.scale, v = v / this.scale, i.drawImage( 734 | s, 735 | -m - u / this.scale, 736 | -v - g / this.scale, 737 | u / this.scale, 738 | g / this.scale 739 | )) : (i.rotate(r * 90 * Math.PI / 180), i.drawImage(s, -m - u, -v - g, u, g)); 740 | break; 741 | case 3: 742 | case -1: 743 | this.full ? (f(d / this.scale, C / this.scale), m = m / this.scale + (u / this.scale - g / this.scale) / 2, v = v / this.scale + (g / this.scale - u / this.scale) / 2, i.rotate(r * 90 * Math.PI / 180), i.drawImage( 744 | s, 745 | -v - u / this.scale, 746 | m, 747 | u / this.scale, 748 | g / this.scale 749 | )) : (m = m + (u - g) / 2, v = v + (g - u) / 2, i.rotate(r * 90 * Math.PI / 180), i.drawImage(s, -v - u, m, u, g)); 750 | break; 751 | default: 752 | this.full ? (f(d / this.scale, C / this.scale), i.drawImage( 753 | s, 754 | m / this.scale, 755 | v / this.scale, 756 | u / this.scale, 757 | g / this.scale 758 | )) : i.drawImage(s, m, v, u, g); 759 | } 760 | i.restore(); 761 | } else { 762 | let p = o * this.scale, d = h * this.scale; 763 | switch (i.save(), r) { 764 | case 0: 765 | f(p, d), i.drawImage(s, 0, 0, p, d); 766 | break; 767 | case 1: 768 | case -3: 769 | f(d, p), i.rotate(r * 90 * Math.PI / 180), i.drawImage(s, 0, -d, p, d); 770 | break; 771 | case 2: 772 | case -2: 773 | f(p, d), i.rotate(r * 90 * Math.PI / 180), i.drawImage(s, -p, -d, p, d); 774 | break; 775 | case 3: 776 | case -1: 777 | f(d, p), i.rotate(r * 90 * Math.PI / 180), i.drawImage(s, -p, 0, p, d); 778 | break; 779 | default: 780 | f(p, d), i.drawImage(s, 0, 0, p, d); 781 | } 782 | i.restore(); 783 | } 784 | t(e); 785 | }; 786 | var c = this.img.substr(0, 4); 787 | c !== "data" && (s.crossOrigin = "Anonymous"), s.src = this.imgs; 788 | const l = this.fillColor; 789 | function f(p, d) { 790 | e.width = Math.round(p), e.height = Math.round(d), l && (i.fillStyle = l, i.fillRect(0, 0, e.width, e.height)); 791 | } 792 | }, 793 | // 获取转换成base64 的图片信息 794 | getCropData(t) { 795 | this.getCropChecked((e) => { 796 | t(e.toDataURL("image/" + this.outputType, this.outputSize)); 797 | }); 798 | }, 799 | //canvas获取为blob对象 800 | getCropBlob(t) { 801 | this.getCropChecked((e) => { 802 | e.toBlob( 803 | (i) => t(i), 804 | "image/" + this.outputType, 805 | this.outputSize 806 | ); 807 | }); 808 | }, 809 | // 自动预览函数 810 | showPreview() { 811 | if (this.isCanShow) 812 | this.isCanShow = !1, setTimeout(() => { 813 | this.isCanShow = !0; 814 | }, 16); 815 | else 816 | return !1; 817 | let t = this.cropW, e = this.cropH, i = this.scale; 818 | var s = {}; 819 | s.div = { 820 | width: `${t}px`, 821 | height: `${e}px` 822 | }; 823 | let r = (this.x - this.cropOffsertX) / i, o = (this.y - this.cropOffsertY) / i, h = 0; 824 | s.w = t, s.h = e, s.url = this.imgs, s.img = { 825 | width: `${this.trueWidth}px`, 826 | height: `${this.trueHeight}px`, 827 | transform: `scale(${i})translate3d(${r}px, ${o}px, ${h}px)rotateZ(${this.rotate * 90}deg)` 828 | }, s.html = ` 829 |
830 |
831 | 833 |
834 |
`, this.$emit("real-time", s); 835 | }, 836 | // reload 图片布局函数 837 | reload() { 838 | let t = new Image(); 839 | t.onload = () => { 840 | this.w = parseFloat(window.getComputedStyle(this.$refs.cropper).width), this.h = parseFloat(window.getComputedStyle(this.$refs.cropper).height), this.trueWidth = t.width, this.trueHeight = t.height, this.original ? this.scale = 1 : this.scale = this.checkedMode(), this.$nextTick(() => { 841 | this.x = -(this.trueWidth - this.trueWidth * this.scale) / 2 + (this.w - this.trueWidth * this.scale) / 2, this.y = -(this.trueHeight - this.trueHeight * this.scale) / 2 + (this.h - this.trueHeight * this.scale) / 2, this.loading = !1, this.autoCrop && this.goAutoCrop(), this.$emit("img-load", "success"), setTimeout(() => { 842 | this.showPreview(); 843 | }, 20); 844 | }); 845 | }, t.onerror = () => { 846 | this.$emit("img-load", "error"); 847 | }, t.src = this.imgs; 848 | }, 849 | // 背景布局的函数 850 | checkedMode() { 851 | let t = 1, e = this.trueWidth, i = this.trueHeight; 852 | const s = this.mode.split(" "); 853 | switch (s[0]) { 854 | case "contain": 855 | this.trueWidth > this.w && (t = this.w / this.trueWidth), this.trueHeight * t > this.h && (t = this.h / this.trueHeight); 856 | break; 857 | case "cover": 858 | e = this.w, t = e / this.trueWidth, i = i * t, i < this.h && (i = this.h, t = i / this.trueHeight); 859 | break; 860 | default: 861 | try { 862 | let r = s[0]; 863 | if (r.search("px") !== -1) { 864 | r = r.replace("px", ""), e = parseFloat(r); 865 | const o = e / this.trueWidth; 866 | let h = 1, a = s[1]; 867 | a.search("px") !== -1 && (a = a.replace("px", ""), i = parseFloat(a), h = i / this.trueHeight), t = Math.min(o, h); 868 | } 869 | if (r.search("%") !== -1 && (r = r.replace("%", ""), e = parseFloat(r) / 100 * this.w, t = e / this.trueWidth), s.length === 2 && r === "auto") { 870 | let o = s[1]; 871 | o.search("px") !== -1 && (o = o.replace("px", ""), i = parseFloat(o), t = i / this.trueHeight), o.search("%") !== -1 && (o = o.replace("%", ""), i = parseFloat(o) / 100 * this.h, t = i / this.trueHeight); 872 | } 873 | } catch { 874 | t = 1; 875 | } 876 | } 877 | return t; 878 | }, 879 | // 自动截图函数 880 | goAutoCrop(t, e) { 881 | if (this.imgs === "" || this.imgs === null) 882 | return; 883 | this.clearCrop(), this.cropping = !0; 884 | let i = this.w, s = this.h; 885 | if (this.centerBox) { 886 | const h = Math.abs(this.rotate) % 2 > 0; 887 | let a = (h ? this.trueHeight : this.trueWidth) * this.scale, n = (h ? this.trueWidth : this.trueHeight) * this.scale; 888 | i = a < i ? a : i, s = n < s ? n : s; 889 | } 890 | var r = t || parseFloat(this.autoCropWidth), o = e || parseFloat(this.autoCropHeight); 891 | (r === 0 || o === 0) && (r = i * 0.8, o = s * 0.8), r = r > i ? i : r, o = o > s ? s : o, this.fixed && (o = r / this.fixedNumber[0] * this.fixedNumber[1]), o > this.h && (o = this.h, r = o / this.fixedNumber[1] * this.fixedNumber[0]), this.changeCrop(r, o); 892 | }, 893 | // 手动改变截图框大小函数 894 | changeCrop(t, e) { 895 | if (this.centerBox) { 896 | let i = this.getImgAxis(); 897 | t > i.x2 - i.x1 && (t = i.x2 - i.x1, e = t / this.fixedNumber[0] * this.fixedNumber[1]), e > i.y2 - i.y1 && (e = i.y2 - i.y1, t = e / this.fixedNumber[1] * this.fixedNumber[0]); 898 | } 899 | this.cropW = t, this.cropH = e, this.checkCropLimitSize(), this.$nextTick(() => { 900 | this.cropOffsertX = (this.w - this.cropW) / 2, this.cropOffsertY = (this.h - this.cropH) / 2, this.centerBox && this.moveCrop(null, !0); 901 | }); 902 | }, 903 | // 重置函数, 恢复组件置初始状态 904 | refresh() { 905 | this.img, this.imgs = "", this.scale = 1, this.crop = !1, this.rotate = 0, this.w = 0, this.h = 0, this.trueWidth = 0, this.trueHeight = 0, this.imgIsQqualCrop = !1, this.clearCrop(), this.$nextTick(() => { 906 | this.checkedImg(); 907 | }); 908 | }, 909 | // 向左边旋转 910 | rotateLeft() { 911 | this.rotate = this.rotate <= -3 ? 0 : this.rotate - 1; 912 | }, 913 | // 向右边旋转 914 | rotateRight() { 915 | this.rotate = this.rotate >= 3 ? 0 : this.rotate + 1; 916 | }, 917 | // 清除旋转 918 | rotateClear() { 919 | this.rotate = 0; 920 | }, 921 | // 图片坐标点校验 922 | checkoutImgAxis(t, e, i) { 923 | t = t || this.x, e = e || this.y, i = i || this.scale; 924 | let s = !0; 925 | if (this.centerBox) { 926 | let r = this.getImgAxis(t, e, i), o = this.getCropAxis(); 927 | r.x1 >= o.x1 && (s = !1), r.x2 <= o.x2 && (s = !1), r.y1 >= o.y1 && (s = !1), r.y2 <= o.y2 && (s = !1), s || this.changeImgScale(r, o, i); 928 | } 929 | return s; 930 | }, 931 | // 缩放图片,将图片坐标适配截图框坐标 932 | changeImgScale(t, e, i) { 933 | let s = this.trueWidth, r = this.trueHeight, o = s * i, h = r * i; 934 | if (o >= this.cropW && h >= this.cropH) 935 | this.scale = i; 936 | else { 937 | const a = this.cropW / s, n = this.cropH / r, c = this.cropH <= r * a ? a : n; 938 | this.scale = c, o = s * c, h = r * c; 939 | } 940 | this.imgIsQqualCrop || (t.x1 >= e.x1 && (this.isRotateRightOrLeft ? this.x = e.x1 - (s - o) / 2 - (o - h) / 2 : this.x = e.x1 - (s - o) / 2), t.x2 <= e.x2 && (this.isRotateRightOrLeft ? this.x = e.x1 - (s - o) / 2 - (o - h) / 2 - h + this.cropW : this.x = e.x2 - (s - o) / 2 - o), t.y1 >= e.y1 && (this.isRotateRightOrLeft ? this.y = e.y1 - (r - h) / 2 - (h - o) / 2 : this.y = e.y1 - (r - h) / 2), t.y2 <= e.y2 && (this.isRotateRightOrLeft ? this.y = e.y2 - (r - h) / 2 - (h - o) / 2 - o : this.y = e.y2 - (r - h) / 2 - h)), (o < this.cropW || h < this.cropH) && (this.imgIsQqualCrop = !0); 941 | } 942 | }, 943 | mounted() { 944 | this.support = "onwheel" in document.createElement("div") ? "wheel" : document.onmousewheel !== void 0 ? "mousewheel" : "DOMMouseScroll"; 945 | let t = this; 946 | var e = navigator.userAgent; 947 | this.isIOS = !!e.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), HTMLCanvasElement.prototype.toBlob || Object.defineProperty(HTMLCanvasElement.prototype, "toBlob", { 948 | value: function(i, s, r) { 949 | for (var o = atob(this.toDataURL(s, r).split(",")[1]), h = o.length, a = new Uint8Array(h), n = 0; n < h; n++) 950 | a[n] = o.charCodeAt(n); 951 | i(new Blob([a], { type: t.type || "image/png" })); 952 | } 953 | }), this.showPreview(), this.checkedImg(); 954 | }, 955 | unmounted() { 956 | window.removeEventListener("mousemove", this.moveCrop), window.removeEventListener("mouseup", this.leaveCrop), window.removeEventListener("touchmove", this.moveCrop), window.removeEventListener("touchend", this.leaveCrop), this.cancelScale(); 957 | } 958 | }), A = { 959 | key: 0, 960 | class: "cropper-box" 961 | }, B = ["src"], P = { class: "cropper-view-box" }, R = ["src"], D = { key: 1 }; 962 | function U(t, e, i, s, r, o) { 963 | return y(), x("div", { 964 | class: "vue-cropper", 965 | ref: "cropper", 966 | onMouseover: e[28] || (e[28] = (...h) => t.scaleImg && t.scaleImg(...h)), 967 | onMouseout: e[29] || (e[29] = (...h) => t.cancelScale && t.cancelScale(...h)) 968 | }, [ 969 | t.imgs ? (y(), x("div", A, [ 970 | H(w("div", { 971 | class: "cropper-box-canvas", 972 | style: b({ 973 | width: t.trueWidth + "px", 974 | height: t.trueHeight + "px", 975 | transform: "scale(" + t.scale + "," + t.scale + ") translate3d(" + t.x / t.scale + "px," + t.y / t.scale + "px,0)rotateZ(" + t.rotate * 90 + "deg)" 976 | }) 977 | }, [ 978 | w("img", { 979 | src: t.imgs, 980 | alt: "cropper-img", 981 | ref: "cropperImg" 982 | }, null, 8, B) 983 | ], 4), [ 984 | [W, !t.loading] 985 | ]) 986 | ])) : O("", !0), 987 | w("div", { 988 | class: I(["cropper-drag-box", { "cropper-move": t.move && !t.crop, "cropper-crop": t.crop, "cropper-modal": t.cropping }]), 989 | onMousedown: e[0] || (e[0] = (...h) => t.startMove && t.startMove(...h)), 990 | onTouchstart: e[1] || (e[1] = (...h) => t.startMove && t.startMove(...h)) 991 | }, null, 34), 992 | H(w("div", { 993 | class: "cropper-crop-box", 994 | style: b({ 995 | width: t.cropW + "px", 996 | height: t.cropH + "px", 997 | transform: "translate3d(" + t.cropOffsertX + "px," + t.cropOffsertY + "px,0)" 998 | }) 999 | }, [ 1000 | w("span", P, [ 1001 | w("img", { 1002 | style: b({ 1003 | width: t.trueWidth + "px", 1004 | height: t.trueHeight + "px", 1005 | transform: "scale(" + t.scale + "," + t.scale + ") translate3d(" + (t.x - t.cropOffsertX) / t.scale + "px," + (t.y - t.cropOffsertY) / t.scale + "px,0)rotateZ(" + t.rotate * 90 + "deg)" 1006 | }), 1007 | src: t.imgs, 1008 | alt: "cropper-img" 1009 | }, null, 12, R) 1010 | ]), 1011 | w("span", { 1012 | class: "cropper-face cropper-move", 1013 | onMousedown: e[2] || (e[2] = (...h) => t.cropMove && t.cropMove(...h)), 1014 | onTouchstart: e[3] || (e[3] = (...h) => t.cropMove && t.cropMove(...h)) 1015 | }, null, 32), 1016 | t.info ? (y(), x("span", { 1017 | key: 0, 1018 | class: "crop-info", 1019 | style: b({ top: t.cropInfo.top }) 1020 | }, X(t.cropInfo.width) + " × " + X(t.cropInfo.height), 5)) : O("", !0), 1021 | t.fixedBox ? O("", !0) : (y(), x("span", D, [ 1022 | w("span", { 1023 | class: "crop-line line-w", 1024 | onMousedown: e[4] || (e[4] = (h) => t.changeCropSize(h, !1, !0, 0, 1)), 1025 | onTouchstart: e[5] || (e[5] = (h) => t.changeCropSize(h, !1, !0, 0, 1)) 1026 | }, null, 32), 1027 | w("span", { 1028 | class: "crop-line line-a", 1029 | onMousedown: e[6] || (e[6] = (h) => t.changeCropSize(h, !0, !1, 1, 0)), 1030 | onTouchstart: e[7] || (e[7] = (h) => t.changeCropSize(h, !0, !1, 1, 0)) 1031 | }, null, 32), 1032 | w("span", { 1033 | class: "crop-line line-s", 1034 | onMousedown: e[8] || (e[8] = (h) => t.changeCropSize(h, !1, !0, 0, 2)), 1035 | onTouchstart: e[9] || (e[9] = (h) => t.changeCropSize(h, !1, !0, 0, 2)) 1036 | }, null, 32), 1037 | w("span", { 1038 | class: "crop-line line-d", 1039 | onMousedown: e[10] || (e[10] = (h) => t.changeCropSize(h, !0, !1, 2, 0)), 1040 | onTouchstart: e[11] || (e[11] = (h) => t.changeCropSize(h, !0, !1, 2, 0)) 1041 | }, null, 32), 1042 | w("span", { 1043 | class: "crop-point point1", 1044 | onMousedown: e[12] || (e[12] = (h) => t.changeCropSize(h, !0, !0, 1, 1)), 1045 | onTouchstart: e[13] || (e[13] = (h) => t.changeCropSize(h, !0, !0, 1, 1)) 1046 | }, null, 32), 1047 | w("span", { 1048 | class: "crop-point point2", 1049 | onMousedown: e[14] || (e[14] = (h) => t.changeCropSize(h, !1, !0, 0, 1)), 1050 | onTouchstart: e[15] || (e[15] = (h) => t.changeCropSize(h, !1, !0, 0, 1)) 1051 | }, null, 32), 1052 | w("span", { 1053 | class: "crop-point point3", 1054 | onMousedown: e[16] || (e[16] = (h) => t.changeCropSize(h, !0, !0, 2, 1)), 1055 | onTouchstart: e[17] || (e[17] = (h) => t.changeCropSize(h, !0, !0, 2, 1)) 1056 | }, null, 32), 1057 | w("span", { 1058 | class: "crop-point point4", 1059 | onMousedown: e[18] || (e[18] = (h) => t.changeCropSize(h, !0, !1, 1, 0)), 1060 | onTouchstart: e[19] || (e[19] = (h) => t.changeCropSize(h, !0, !1, 1, 0)) 1061 | }, null, 32), 1062 | w("span", { 1063 | class: "crop-point point5", 1064 | onMousedown: e[20] || (e[20] = (h) => t.changeCropSize(h, !0, !1, 2, 0)), 1065 | onTouchstart: e[21] || (e[21] = (h) => t.changeCropSize(h, !0, !1, 2, 0)) 1066 | }, null, 32), 1067 | w("span", { 1068 | class: "crop-point point6", 1069 | onMousedown: e[22] || (e[22] = (h) => t.changeCropSize(h, !0, !0, 1, 2)), 1070 | onTouchstart: e[23] || (e[23] = (h) => t.changeCropSize(h, !0, !0, 1, 2)) 1071 | }, null, 32), 1072 | w("span", { 1073 | class: "crop-point point7", 1074 | onMousedown: e[24] || (e[24] = (h) => t.changeCropSize(h, !1, !0, 0, 2)), 1075 | onTouchstart: e[25] || (e[25] = (h) => t.changeCropSize(h, !1, !0, 0, 2)) 1076 | }, null, 32), 1077 | w("span", { 1078 | class: "crop-point point8", 1079 | onMousedown: e[26] || (e[26] = (h) => t.changeCropSize(h, !0, !0, 2, 2)), 1080 | onTouchstart: e[27] || (e[27] = (h) => t.changeCropSize(h, !0, !0, 2, 2)) 1081 | }, null, 32) 1082 | ])) 1083 | ], 4), [ 1084 | [W, t.cropping] 1085 | ]) 1086 | ], 544); 1087 | } 1088 | const M = /* @__PURE__ */ $(z, [["render", U], ["__scopeId", "data-v-a742df44"]]), F = function(t) { 1089 | t.component("VueCropper", M); 1090 | }, V = { 1091 | version: "1.1.4", 1092 | install: F, 1093 | VueCropper: M 1094 | }; 1095 | export { 1096 | M as VueCropper, 1097 | V as default, 1098 | V as globalCropper 1099 | }; 1100 | --------------------------------------------------------------------------------