├── .eslintrc.cjs ├── .github └── workflows │ ├── gh-pages.yml │ └── npm_publish.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── dist ├── index.d.ts ├── index.es.js ├── index.umd.js └── shims-vue.d.ts ├── example ├── .eslintrc.cjs ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode │ └── extensions.json ├── demo │ ├── assets │ │ ├── demo-d54cd5f0.jpg │ │ ├── index-987b8e67.js │ │ └── index-eb952961.css │ └── index.html ├── env.d.ts ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── App.vue │ ├── CanvasModal.vue │ ├── assets │ │ ├── app.css │ │ └── demo.jpg │ └── main.ts ├── tsconfig.config.json ├── tsconfig.json └── vite.config.ts ├── package-lock.json ├── package.json ├── src ├── VueCropper.vue ├── index.ts ├── shims-vue.d.ts └── types.ts ├── tsconfig.config.json ├── tsconfig.json └── vite.config.ts /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require("@rushstack/eslint-patch/modern-module-resolution"); 3 | 4 | module.exports = { 5 | "root": true, 6 | "extends": [ 7 | "plugin:vue/vue3-essential", 8 | "eslint:recommended", 9 | "@vue/eslint-config-typescript/recommended", 10 | "@vue/eslint-config-prettier" 11 | ], 12 | "env": { 13 | "vue/setup-compiler-macros": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | # on 是 Actions 的触发条件,这里的配置说明当 master 分支有提交的时候,根据这个配置文件执行 4 | on: 5 | push: 6 | branches: 7 | - master # Set a branch to deploy 8 | 9 | # jobs 是要执行的任务,我们看到他要执行 deploy 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest # 运行环境 13 | steps: # 执行步骤 14 | 15 | # checkout 分支 16 | - uses: actions/checkout@v3 17 | - run: cp -r example/demo .temp 18 | 19 | - uses: actions/checkout@v3 20 | with: 21 | ref: gh-pages 22 | clean: false 23 | 24 | - run: cp -rf .temp/* . 25 | - run: rm -r .temp 26 | - run: git config user.name "${{ github.actor }}" 27 | - run: git config user.email "${{ github.actor }}@users.noreply.github.com" 28 | - run: git add --all 29 | - run: git commit --message "${{ github.ref_name }}" 30 | - run: git push 31 | -------------------------------------------------------------------------------- /.github/workflows/npm_publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - '*' 5 | 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: 16 14 | - run: npm install 15 | - uses: JS-DevTools/npm-publish@v1 16 | with: 17 | token: ${{ secrets.NPM_ACCESS_TOKEN }} 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist-ssr 13 | coverage 14 | *.local 15 | 16 | /cypress/videos/ 17 | /cypress/screenshots/ 18 | 19 | # Editor directories and files 20 | .vscode/* 21 | !.vscode/extensions.json 22 | .idea 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | .local 3 | /node_modules/** 4 | /example/src/assets/* 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | semi: false # 语句末尾是否加分号 2 | singleQuote: true # 使用单引号代替双引号 3 | printWidth: 100 # 超过多长进行换行 4 | trailingComma: 'none' # 多行输入的尾逗号是否添加 5 | arrowParens: 'avoid' # (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号 6 | endOfLine: 'lf' # 格式化换行符,默认值 lf 7 | # vueIndentScriptAndStyle: true # vue script 标签的缩进开启 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Hccake 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-cropper 2 | 3 | A Vue image cropper components by cropperjs. 4 | 5 | Github: https://github.com/ballcat-projects/vue-cropper 6 | 7 | ## Demo 8 | 9 | online demo: https://ballcat-projects.github.io/vue-cropper 10 | 11 | You can also clone the repository and run the demo locally: 12 | 13 | ```shell 14 | # clone 15 | git clone https://github.com/ballcat-projects/vue-cropper.git 16 | 17 | # enter the folder 18 | cd vue-cropper/example 19 | 20 | # install dependency 21 | npm install 22 | 23 | # run it 24 | npm run dev 25 | ``` 26 | 27 | ## Getting started 28 | 29 | ### Installation 30 | 31 | ```npm 32 | npm install @ballcat/vue-cropper 33 | ``` 34 | or 35 | ```yarn 36 | yarn add @ballcat/vue-cropper 37 | ``` 38 | 39 | ### Usage 40 | 41 | #### Global Registration 42 | 43 | ```vue 44 | import { createApp } from 'vue'; 45 | import App from './App'; 46 | 47 | import VueCropper from '@ballcat/vue-cropper'; 48 | import 'cropperjs/dist/cropper.css'; 49 | 50 | const app = createApp(App); 51 | 52 | app.use(VueCropper).mount('#app'); 53 | ``` 54 | 55 | #### Local Registration 56 | 57 | ```vue 58 | 65 | 89 | ``` 90 | 91 | or use setup script 92 | 93 | ```vue 94 | 106 | ``` 107 | 108 | ## API 109 | 110 | VueCropper props that can be used are divided into two parts, custom and all properties supported by cropperjs 111 | 112 | ### custom options 113 | 114 | | Property | Description | Type | Required | 115 | | :------------- | :------------------------------------------ | :------ | :------- | 116 | | src | origin image src | string | true | 117 | | imgStyle | the img element style | object | -- | 118 | | imgCrossOrigin | the img element crossOrigin attribute value | string | -- | 119 | | alt | the img element alt attribute value | boolean | -- | 120 | 121 | ### Cropperjs options 122 | 123 | see [cropperjs document](https://github.com/fengyuanchen/cropperjs/blob/main/README.md) 124 | 125 | 126 | ### custom expose method 127 | 128 | | Method | Description | Type | 129 | |:--------|:------------------------------|:-----------| 130 | | flipX | flip the picture horizontally | () => void | 131 | | flipY | flip the picture vertically | () => void | 132 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { AllowedComponentProps } from 'vue'; 2 | import type { ComponentCustomProps } from 'vue'; 3 | import type { ComponentOptionsMixin } from 'vue'; 4 | import { default as Cropper_2 } from 'cropperjs'; 5 | import type { CSSProperties } from 'vue'; 6 | import type { DefineComponent } from 'vue'; 7 | import type { ExtractPropTypes } from 'vue'; 8 | import type { PropType } from 'vue'; 9 | import type { Ref } from 'vue'; 10 | import type { VNodeProps } from 'vue'; 11 | 12 | declare const _sfc_main: DefineComponent<{ 13 | src: { 14 | type: StringConstructor; 15 | required: true; 16 | }; 17 | alt: { 18 | type: StringConstructor; 19 | default: string; 20 | }; 21 | imgStyle: { 22 | type: PropType; 23 | default: () => {}; 24 | }; 25 | imgCrossOrigin: { 26 | type: PropType<"" | "anonymous" | "use-credentials" | undefined>; 27 | default: undefined; 28 | }; 29 | viewMode: { 30 | type: PropType; 31 | default: number; 32 | }; 33 | dragMode: { 34 | type: PropType; 35 | default: string; 36 | }; 37 | initialAspectRatio: { 38 | type: NumberConstructor; 39 | default: number; 40 | }; 41 | aspectRatio: { 42 | type: NumberConstructor; 43 | default: number; 44 | }; 45 | data: { 46 | type: PropType; 47 | default: undefined; 48 | }; 49 | preview: { 50 | type: PropType | undefined>; 51 | default: string; 52 | }; 53 | responsive: { 54 | type: BooleanConstructor; 55 | default: boolean; 56 | }; 57 | restore: { 58 | type: BooleanConstructor; 59 | default: boolean; 60 | }; 61 | checkCrossOrigin: { 62 | type: BooleanConstructor; 63 | default: boolean; 64 | }; 65 | checkOrientation: { 66 | type: BooleanConstructor; 67 | default: boolean; 68 | }; 69 | modal: { 70 | type: BooleanConstructor; 71 | default: boolean; 72 | }; 73 | guides: { 74 | type: BooleanConstructor; 75 | default: boolean; 76 | }; 77 | center: { 78 | type: BooleanConstructor; 79 | default: boolean; 80 | }; 81 | highlight: { 82 | type: BooleanConstructor; 83 | default: boolean; 84 | }; 85 | background: { 86 | type: BooleanConstructor; 87 | default: boolean; 88 | }; 89 | autoCrop: { 90 | type: BooleanConstructor; 91 | default: boolean; 92 | }; 93 | autoCropArea: { 94 | type: NumberConstructor; 95 | default: number; 96 | }; 97 | movable: { 98 | type: BooleanConstructor; 99 | default: boolean; 100 | }; 101 | rotatable: { 102 | type: BooleanConstructor; 103 | default: boolean; 104 | }; 105 | scalable: { 106 | type: BooleanConstructor; 107 | default: boolean; 108 | }; 109 | zoomable: { 110 | type: BooleanConstructor; 111 | default: boolean; 112 | }; 113 | zoomOnTouch: { 114 | type: BooleanConstructor; 115 | default: boolean; 116 | }; 117 | zoomOnWheel: { 118 | type: BooleanConstructor; 119 | default: boolean; 120 | }; 121 | wheelZoomRatio: { 122 | type: NumberConstructor; 123 | default: number; 124 | }; 125 | cropBoxMovable: { 126 | type: BooleanConstructor; 127 | default: boolean; 128 | }; 129 | cropBoxResizable: { 130 | type: BooleanConstructor; 131 | default: boolean; 132 | }; 133 | toggleDragModeOnDblclick: { 134 | type: BooleanConstructor; 135 | default: boolean; 136 | }; 137 | minCanvasWidth: { 138 | type: NumberConstructor; 139 | default: number; 140 | }; 141 | minCanvasHeight: { 142 | type: NumberConstructor; 143 | default: number; 144 | }; 145 | minCropBoxWidth: { 146 | type: NumberConstructor; 147 | default: number; 148 | }; 149 | minCropBoxHeight: { 150 | type: NumberConstructor; 151 | default: number; 152 | }; 153 | minContainerWidth: { 154 | type: NumberConstructor; 155 | default: number; 156 | }; 157 | minContainerHeight: { 158 | type: NumberConstructor; 159 | default: number; 160 | }; 161 | ready: { 162 | type: PropType<(event: Cropper_2.ReadyEvent) => void>; 163 | default: undefined; 164 | }; 165 | cropstart: { 166 | type: PropType<(event: Cropper_2.CropStartEvent) => void>; 167 | default: undefined; 168 | }; 169 | cropmove: { 170 | type: PropType<(event: Cropper_2.CropMoveEvent) => void>; 171 | default: undefined; 172 | }; 173 | cropend: { 174 | type: PropType<(event: Cropper_2.CropEndEvent) => void>; 175 | default: undefined; 176 | }; 177 | crop: { 178 | type: PropType<(event: Cropper_2.CropEvent) => void>; 179 | default: undefined; 180 | }; 181 | zoom: { 182 | type: PropType<(event: Cropper_2.ZoomEvent) => void>; 183 | default: undefined; 184 | }; 185 | }, { 186 | imageStyle: { 187 | display: string; 188 | maxWidth: string; 189 | }; 190 | props: any; 191 | imageRef: Ref; 192 | cropper: Cropper_2 | undefined; 193 | initCropper: () => void; 194 | }, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, VNodeProps & AllowedComponentProps & ComponentCustomProps, Readonly; 205 | default: () => {}; 206 | }; 207 | imgCrossOrigin: { 208 | type: PropType<"" | "anonymous" | "use-credentials" | undefined>; 209 | default: undefined; 210 | }; 211 | viewMode: { 212 | type: PropType; 213 | default: number; 214 | }; 215 | dragMode: { 216 | type: PropType; 217 | default: string; 218 | }; 219 | initialAspectRatio: { 220 | type: NumberConstructor; 221 | default: number; 222 | }; 223 | aspectRatio: { 224 | type: NumberConstructor; 225 | default: number; 226 | }; 227 | data: { 228 | type: PropType; 229 | default: undefined; 230 | }; 231 | preview: { 232 | type: PropType | undefined>; 233 | default: string; 234 | }; 235 | responsive: { 236 | type: BooleanConstructor; 237 | default: boolean; 238 | }; 239 | restore: { 240 | type: BooleanConstructor; 241 | default: boolean; 242 | }; 243 | checkCrossOrigin: { 244 | type: BooleanConstructor; 245 | default: boolean; 246 | }; 247 | checkOrientation: { 248 | type: BooleanConstructor; 249 | default: boolean; 250 | }; 251 | modal: { 252 | type: BooleanConstructor; 253 | default: boolean; 254 | }; 255 | guides: { 256 | type: BooleanConstructor; 257 | default: boolean; 258 | }; 259 | center: { 260 | type: BooleanConstructor; 261 | default: boolean; 262 | }; 263 | highlight: { 264 | type: BooleanConstructor; 265 | default: boolean; 266 | }; 267 | background: { 268 | type: BooleanConstructor; 269 | default: boolean; 270 | }; 271 | autoCrop: { 272 | type: BooleanConstructor; 273 | default: boolean; 274 | }; 275 | autoCropArea: { 276 | type: NumberConstructor; 277 | default: number; 278 | }; 279 | movable: { 280 | type: BooleanConstructor; 281 | default: boolean; 282 | }; 283 | rotatable: { 284 | type: BooleanConstructor; 285 | default: boolean; 286 | }; 287 | scalable: { 288 | type: BooleanConstructor; 289 | default: boolean; 290 | }; 291 | zoomable: { 292 | type: BooleanConstructor; 293 | default: boolean; 294 | }; 295 | zoomOnTouch: { 296 | type: BooleanConstructor; 297 | default: boolean; 298 | }; 299 | zoomOnWheel: { 300 | type: BooleanConstructor; 301 | default: boolean; 302 | }; 303 | wheelZoomRatio: { 304 | type: NumberConstructor; 305 | default: number; 306 | }; 307 | cropBoxMovable: { 308 | type: BooleanConstructor; 309 | default: boolean; 310 | }; 311 | cropBoxResizable: { 312 | type: BooleanConstructor; 313 | default: boolean; 314 | }; 315 | toggleDragModeOnDblclick: { 316 | type: BooleanConstructor; 317 | default: boolean; 318 | }; 319 | minCanvasWidth: { 320 | type: NumberConstructor; 321 | default: number; 322 | }; 323 | minCanvasHeight: { 324 | type: NumberConstructor; 325 | default: number; 326 | }; 327 | minCropBoxWidth: { 328 | type: NumberConstructor; 329 | default: number; 330 | }; 331 | minCropBoxHeight: { 332 | type: NumberConstructor; 333 | default: number; 334 | }; 335 | minContainerWidth: { 336 | type: NumberConstructor; 337 | default: number; 338 | }; 339 | minContainerHeight: { 340 | type: NumberConstructor; 341 | default: number; 342 | }; 343 | ready: { 344 | type: PropType<(event: Cropper_2.ReadyEvent) => void>; 345 | default: undefined; 346 | }; 347 | cropstart: { 348 | type: PropType<(event: Cropper_2.CropStartEvent) => void>; 349 | default: undefined; 350 | }; 351 | cropmove: { 352 | type: PropType<(event: Cropper_2.CropMoveEvent) => void>; 353 | default: undefined; 354 | }; 355 | cropend: { 356 | type: PropType<(event: Cropper_2.CropEndEvent) => void>; 357 | default: undefined; 358 | }; 359 | crop: { 360 | type: PropType<(event: Cropper_2.CropEvent) => void>; 361 | default: undefined; 362 | }; 363 | zoom: { 364 | type: PropType<(event: Cropper_2.ZoomEvent) => void>; 365 | default: undefined; 366 | }; 367 | }>>, { 368 | crop: (event: Cropper_2.CropEvent) => void; 369 | preview: string | HTMLElement | HTMLElement[] | NodeListOf | undefined; 370 | alt: string; 371 | imgStyle: CSSProperties; 372 | imgCrossOrigin: "" | "anonymous" | "use-credentials" | undefined; 373 | viewMode: Cropper_2.ViewMode; 374 | dragMode: Cropper_2.DragMode; 375 | initialAspectRatio: number; 376 | aspectRatio: number; 377 | data: Cropper_2.SetDataOptions; 378 | responsive: boolean; 379 | restore: boolean; 380 | checkCrossOrigin: boolean; 381 | checkOrientation: boolean; 382 | modal: boolean; 383 | guides: boolean; 384 | center: boolean; 385 | highlight: boolean; 386 | background: boolean; 387 | autoCrop: boolean; 388 | autoCropArea: number; 389 | movable: boolean; 390 | rotatable: boolean; 391 | scalable: boolean; 392 | zoomable: boolean; 393 | zoomOnTouch: boolean; 394 | zoomOnWheel: boolean; 395 | wheelZoomRatio: number; 396 | cropBoxMovable: boolean; 397 | cropBoxResizable: boolean; 398 | toggleDragModeOnDblclick: boolean; 399 | minCanvasWidth: number; 400 | minCanvasHeight: number; 401 | minCropBoxWidth: number; 402 | minCropBoxHeight: number; 403 | minContainerWidth: number; 404 | minContainerHeight: number; 405 | ready: (event: Cropper_2.ReadyEvent) => void; 406 | cropstart: (event: Cropper_2.CropStartEvent) => void; 407 | cropmove: (event: Cropper_2.CropMoveEvent) => void; 408 | cropend: (event: Cropper_2.CropEndEvent) => void; 409 | zoom: (event: Cropper_2.ZoomEvent) => void; 410 | }>; 411 | export default _sfc_main; 412 | 413 | export declare interface VueCropperInstance { 414 | clear(): Cropper_2; 415 | crop(): Cropper_2; 416 | destroy(): Cropper_2; 417 | disable(): Cropper_2; 418 | enable(): Cropper_2; 419 | getCanvasData(): Cropper_2.CanvasData; 420 | getContainerData(): Cropper_2.ContainerData; 421 | getCropBoxData(): Cropper_2.CropBoxData; 422 | getCroppedCanvas(options?: Cropper_2.GetCroppedCanvasOptions): HTMLCanvasElement; 423 | getData(rounded?: boolean): Cropper_2.Data; 424 | getImageData(): Cropper_2.ImageData; 425 | move(offsetX: number, offsetY?: number): Cropper_2; 426 | moveTo(x: number, y?: number): Cropper_2; 427 | replace(url: string, onlyColorChanged?: boolean): Cropper_2; 428 | reset(): Cropper_2; 429 | rotate(degree: number): Cropper_2; 430 | rotateTo(degree: number): Cropper_2; 431 | scale(scaleX: number, scaleY?: number): Cropper_2; 432 | scaleX(scaleX: number): Cropper_2; 433 | scaleY(scaleY: number): Cropper_2; 434 | setAspectRatio(aspectRatio: number): Cropper_2; 435 | setCanvasData(data: Cropper_2.SetCanvasDataOptions): Cropper_2; 436 | setCropBoxData(data: Cropper_2.SetCropBoxDataOptions): Cropper_2; 437 | setData(data: Cropper_2.SetDataOptions): Cropper_2; 438 | setDragMode(dragMode: Cropper_2.DragMode): Cropper_2; 439 | zoom(ratio: number): Cropper_2; 440 | zoomTo(ratio: number, pivot?: { 441 | x: number; 442 | y: number; 443 | }): Cropper_2; 444 | flipX(): void; 445 | flipY(): void; 446 | } 447 | 448 | export { } 449 | -------------------------------------------------------------------------------- /dist/index.es.js: -------------------------------------------------------------------------------- 1 | import { defineComponent as s, ref as r, onMounted as f, watch as m, nextTick as y, openBlock as g, createElementBlock as v, createElementVNode as B, normalizeStyle as C, toRaw as b } from "vue"; 2 | import D from "cropperjs"; 3 | const h = ["src", "alt", "crossorigin"], x = /* @__PURE__ */ s({ 4 | __name: "VueCropper", 5 | props: { 6 | // custom props 7 | src: { 8 | type: String, 9 | required: !0 10 | }, 11 | alt: { 12 | type: String, 13 | default: "image" 14 | }, 15 | imgStyle: { 16 | type: Object, 17 | default: () => ({}) 18 | }, 19 | imgCrossOrigin: { 20 | type: String, 21 | default: void 0 22 | }, 23 | // ========= CropperJS options ======= 24 | // Define the view mode of the cropper 25 | viewMode: { 26 | type: Number, 27 | // 0, 1, 2, 3 28 | default: 0 29 | }, 30 | // Define the dragging mode of the cropper 31 | dragMode: { 32 | type: String, 33 | // 'crop', 'move' or 'none' 34 | default: "crop" 35 | }, 36 | // Define the initial aspect ratio of the crop box 37 | initialAspectRatio: { 38 | type: Number, 39 | default: NaN 40 | }, 41 | // Define the aspect ratio of the crop box 42 | aspectRatio: { 43 | type: Number, 44 | default: NaN 45 | }, 46 | // An object with the previous cropping result data 47 | data: { 48 | type: Object, 49 | default: void 0 50 | }, 51 | // A selector for adding extra containers to preview 52 | preview: { 53 | type: [String, Array, Object], 54 | default: "" 55 | }, 56 | // Re-render the cropper when resize the window 57 | responsive: { 58 | type: Boolean, 59 | default: !0 60 | }, 61 | // Restore the cropped area after resize the window 62 | restore: { 63 | type: Boolean, 64 | default: !0 65 | }, 66 | // Check if the current image is a cross-origin image 67 | checkCrossOrigin: { 68 | type: Boolean, 69 | default: !0 70 | }, 71 | // Check the current image's Exif Orientation information 72 | checkOrientation: { 73 | type: Boolean, 74 | default: !0 75 | }, 76 | // Show the black modal 77 | modal: { 78 | type: Boolean, 79 | default: !0 80 | }, 81 | // Show the dashed lines for guiding 82 | guides: { 83 | type: Boolean, 84 | default: !0 85 | }, 86 | // Show the center indicator for guiding 87 | center: { 88 | type: Boolean, 89 | default: !0 90 | }, 91 | // Show the white modal to highlight the crop box 92 | highlight: { 93 | type: Boolean, 94 | default: !0 95 | }, 96 | // Show the grid background 97 | background: { 98 | type: Boolean, 99 | default: !0 100 | }, 101 | // Enable to crop the image automatically when initialize 102 | autoCrop: { 103 | type: Boolean, 104 | default: !0 105 | }, 106 | // Define the percentage of automatic cropping area when initializes 107 | autoCropArea: { 108 | type: Number, 109 | default: 0.8 110 | }, 111 | // Enable to move the image 112 | movable: { 113 | type: Boolean, 114 | default: !0 115 | }, 116 | // Enable to rotate the image 117 | rotatable: { 118 | type: Boolean, 119 | default: !0 120 | }, 121 | // Enable to scale the image 122 | scalable: { 123 | type: Boolean, 124 | default: !0 125 | }, 126 | // Enable to zoom the image 127 | zoomable: { 128 | type: Boolean, 129 | default: !0 130 | }, 131 | // Enable to zoom the image by dragging touch 132 | zoomOnTouch: { 133 | type: Boolean, 134 | default: !0 135 | }, 136 | // Enable to zoom the image by wheeling mouse 137 | zoomOnWheel: { 138 | type: Boolean, 139 | default: !0 140 | }, 141 | // Define zoom ratio when zoom the image by wheeling mouse 142 | wheelZoomRatio: { 143 | type: Number, 144 | default: 0.1 145 | }, 146 | // Enable to move the crop box 147 | cropBoxMovable: { 148 | type: Boolean, 149 | default: !0 150 | }, 151 | // Enable to resize the crop box 152 | cropBoxResizable: { 153 | type: Boolean, 154 | default: !0 155 | }, 156 | // Toggle drag mode between "crop" and "move" when click twice on the cropper 157 | toggleDragModeOnDblclick: { 158 | type: Boolean, 159 | default: !0 160 | }, 161 | // Size limitation 162 | minCanvasWidth: { 163 | type: Number, 164 | default: 0 165 | }, 166 | minCanvasHeight: { 167 | type: Number, 168 | default: 0 169 | }, 170 | minCropBoxWidth: { 171 | type: Number, 172 | default: 0 173 | }, 174 | minCropBoxHeight: { 175 | type: Number, 176 | default: 0 177 | }, 178 | minContainerWidth: { 179 | type: Number, 180 | default: 200 181 | }, 182 | minContainerHeight: { 183 | type: Number, 184 | default: 100 185 | }, 186 | // Shortcuts of events 187 | ready: { 188 | type: Function, 189 | default: void 0 190 | }, 191 | cropstart: { 192 | type: Function, 193 | default: void 0 194 | }, 195 | cropmove: { 196 | type: Function, 197 | default: void 0 198 | }, 199 | cropend: { 200 | type: Function, 201 | default: void 0 202 | }, 203 | crop: { 204 | type: Function, 205 | default: void 0 206 | }, 207 | zoom: { 208 | type: Function, 209 | default: void 0 210 | } 211 | }, 212 | setup(o, { expose: i }) { 213 | const n = o, d = { 214 | display: "block", 215 | /* This rule is very important, please don't ignore this */ 216 | maxWidth: "100%" 217 | }, u = r(); 218 | let e; 219 | function l() { 220 | n.src ? e = new D(u.value, b(n)) : e = void 0; 221 | } 222 | return f(l), m( 223 | () => n, 224 | () => { 225 | e == null || e.destroy(), y(l); 226 | }, 227 | { deep: !0 } 228 | ), i({ 229 | // Clear the crop box 230 | clear() { 231 | return e == null ? void 0 : e.clear(); 232 | }, 233 | // Show the crop box manually 234 | crop() { 235 | return e == null ? void 0 : e.crop(); 236 | }, 237 | /** 238 | * Destroy the cropper and remove the instance from the image 239 | * @returns {Cropper} this 240 | */ 241 | destroy() { 242 | return e == null ? void 0 : e.destroy(); 243 | }, 244 | // Disable (freeze) the cropper 245 | disable() { 246 | return e == null ? void 0 : e.disable(); 247 | }, 248 | // Enable (unfreeze) the cropper 249 | enable() { 250 | return e == null ? void 0 : e.enable(); 251 | }, 252 | /** 253 | * Get the canvas position and size data. 254 | * @returns {Object} The result canvas data. 255 | */ 256 | getCanvasData() { 257 | return e == null ? void 0 : e.getCanvasData(); 258 | }, 259 | /** 260 | * Get the container size data. 261 | * @returns {Object} The result container data. 262 | */ 263 | getContainerData() { 264 | return e == null ? void 0 : e.getContainerData(); 265 | }, 266 | /** 267 | * Get the crop box position and size data. 268 | * @returns {Object} The result crop box data. 269 | */ 270 | getCropBoxData() { 271 | return e == null ? void 0 : e.getCropBoxData(); 272 | }, 273 | /** 274 | * Get a canvas drawn the cropped image. 275 | * @param {Object} [options={}] - The config options. 276 | * @returns {HTMLCanvasElement} - The result canvas. 277 | */ 278 | getCroppedCanvas(t) { 279 | return e == null ? void 0 : e.getCroppedCanvas(t); 280 | }, 281 | /** 282 | * Get the cropped area position and size data (base on the original image) 283 | * @param {boolean} [rounded=false] - Indicate if round the data values or not. 284 | * @returns {Object} The result cropped data. 285 | */ 286 | getData(t) { 287 | return e == null ? void 0 : e.getData(t); 288 | }, 289 | /** 290 | * Get the image position and size data. 291 | * @returns {Object} The result image data. 292 | */ 293 | getImageData() { 294 | return e == null ? void 0 : e.getImageData(); 295 | }, 296 | /** 297 | * Move the canvas with relative offsets 298 | * @param {number} offsetX - The relative offset distance on the x-axis. 299 | * @param {number} [offsetY=offsetX] - The relative offset distance on the y-axis. 300 | * @returns {Cropper} this 301 | */ 302 | move(t, a) { 303 | return e == null ? void 0 : e.move(t, a); 304 | }, 305 | /** 306 | * Move the canvas to an absolute point 307 | * @param {number} x - The x-axis coordinate. 308 | * @param {number} [y=x] - The y-axis coordinate. 309 | * @returns {Cropper} this 310 | */ 311 | moveTo(t, a) { 312 | return e == null ? void 0 : e.moveTo(t, a); 313 | }, 314 | /** 315 | * Replace the image's src and rebuild the cropper 316 | * @param {string} url - The new URL. 317 | * @param {boolean} [hasSameSize] - Indicate if the new image has the same size as the old one. 318 | * @returns {Cropper} this 319 | */ 320 | replace(t, a) { 321 | return e == null ? void 0 : e.replace(t, a); 322 | }, 323 | // Reset the image and crop box to their initial states 324 | reset() { 325 | return e == null ? void 0 : e.reset(); 326 | }, 327 | /** 328 | * Rotate the canvas with a relative degree 329 | * @param {number} degree - The rotate degree. 330 | * @returns {Cropper} this 331 | */ 332 | rotate(t) { 333 | return e == null ? void 0 : e.rotate(t); 334 | }, 335 | /** 336 | * Rotate the canvas to an absolute degree 337 | * @param {number} degree - The rotate degree. 338 | * @returns {Cropper} this 339 | */ 340 | rotateTo(t) { 341 | return e == null ? void 0 : e.rotateTo(t); 342 | }, 343 | /** 344 | * Scale the image 345 | * @param {number} scaleX - The scale ratio on the x-axis. 346 | * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis. 347 | * @returns {Cropper} this 348 | */ 349 | scale(t, a) { 350 | return e == null ? void 0 : e.scale(t, a); 351 | }, 352 | /** 353 | * Scale the image on the x-axis. 354 | * @param {number} scaleX - The scale ratio on the x-axis. 355 | * @returns {Cropper} this 356 | */ 357 | scaleX(t) { 358 | return e == null ? void 0 : e.scaleX(t); 359 | }, 360 | /** 361 | * Scale the image on the y-axis. 362 | * @param {number} scaleY - The scale ratio on the y-axis. 363 | * @returns {Cropper} this 364 | */ 365 | scaleY(t) { 366 | return e == null ? void 0 : e.scaleY(t); 367 | }, 368 | /** 369 | * Change the aspect ratio of the crop box. 370 | * @param {number} aspectRatio - The new aspect ratio. 371 | * @returns {Cropper} this 372 | */ 373 | setAspectRatio(t) { 374 | return e == null ? void 0 : e.setAspectRatio(t); 375 | }, 376 | /** 377 | * Set the canvas position and size with new data. 378 | * @param {Object} data - The new canvas data. 379 | * @returns {Cropper} this 380 | */ 381 | setCanvasData(t) { 382 | return e == null ? void 0 : e.setCanvasData(t); 383 | }, 384 | /** 385 | * Set the crop box position and size with new data. 386 | * @param {Object} data - The new crop box data. 387 | * @returns {Cropper} this 388 | */ 389 | setCropBoxData(t) { 390 | return e == null ? void 0 : e.setCropBoxData(t); 391 | }, 392 | /** 393 | * Set the cropped area position and size with new data 394 | * @param {Object} data - The new data. 395 | * @returns {Cropper} this 396 | */ 397 | setData(t) { 398 | return e == null ? void 0 : e.setData(t); 399 | }, 400 | /** 401 | * Change the drag mode. 402 | * @param {string} dragMode - The new drag mode. 403 | * @returns {Cropper} this 404 | */ 405 | setDragMode(t) { 406 | return e == null ? void 0 : e.setDragMode(t); 407 | }, 408 | /** 409 | * Zoom the canvas with a relative ratio 410 | * @param {number} ratio - The target ratio. 411 | * @returns {Cropper} this 412 | */ 413 | zoom(t) { 414 | return e == null ? void 0 : e.zoom(t); 415 | }, 416 | /** 417 | * Zoom the canvas to an absolute ratio 418 | * @param {number} ratio - The target ratio. 419 | * @param {Object} pivot - The zoom pivot point coordinate. 420 | * @returns {Cropper} this 421 | */ 422 | zoomTo(t, a) { 423 | return e == null ? void 0 : e.zoomTo(t, a); 424 | }, 425 | /** 426 | * flip the image horizontally 427 | */ 428 | flipX() { 429 | if (e) { 430 | const { scaleX: t } = e.getData(); 431 | e.scaleX(-t); 432 | } 433 | }, 434 | /** 435 | * flip the image vertically 436 | */ 437 | flipY() { 438 | if (e) { 439 | const { scaleY: t } = e.getData(); 440 | e.scaleY(-t); 441 | } 442 | } 443 | }), (t, a) => (g(), v("div", null, [ 444 | B("img", { 445 | ref_key: "imageRef", 446 | ref: u, 447 | src: n.src, 448 | alt: n.alt, 449 | crossorigin: o.imgCrossOrigin, 450 | style: C([d, n.imgStyle]) 451 | }, null, 12, h) 452 | ])); 453 | } 454 | }); 455 | export { 456 | x as default 457 | }; 458 | -------------------------------------------------------------------------------- /dist/index.umd.js: -------------------------------------------------------------------------------- 1 | (function(a,u){typeof exports=="object"&&typeof module<"u"?module.exports=u(require("vue"),require("cropperjs")):typeof define=="function"&&define.amd?define(["vue","cropperjs"],u):(a=typeof globalThis<"u"?globalThis:a||self,a.VueCropper=u(a.Vue,a.CropperJs))})(this,function(a,u){"use strict";const s=["src","alt","crossorigin"];return a.defineComponent({__name:"VueCropper",props:{src:{type:String,required:!0},alt:{type:String,default:"image"},imgStyle:{type:Object,default:()=>({})},imgCrossOrigin:{type:String,default:void 0},viewMode:{type:Number,default:0},dragMode:{type:String,default:"crop"},initialAspectRatio:{type:Number,default:NaN},aspectRatio:{type:Number,default:NaN},data:{type:Object,default:void 0},preview:{type:[String,Array,Object],default:""},responsive:{type:Boolean,default:!0},restore:{type:Boolean,default:!0},checkCrossOrigin:{type:Boolean,default:!0},checkOrientation:{type:Boolean,default:!0},modal:{type:Boolean,default:!0},guides:{type:Boolean,default:!0},center:{type:Boolean,default:!0},highlight:{type:Boolean,default:!0},background:{type:Boolean,default:!0},autoCrop:{type:Boolean,default:!0},autoCropArea:{type:Number,default:.8},movable:{type:Boolean,default:!0},rotatable:{type:Boolean,default:!0},scalable:{type:Boolean,default:!0},zoomable:{type:Boolean,default:!0},zoomOnTouch:{type:Boolean,default:!0},zoomOnWheel:{type:Boolean,default:!0},wheelZoomRatio:{type:Number,default:.1},cropBoxMovable:{type:Boolean,default:!0},cropBoxResizable:{type:Boolean,default:!0},toggleDragModeOnDblclick:{type:Boolean,default:!0},minCanvasWidth:{type:Number,default:0},minCanvasHeight:{type:Number,default:0},minCropBoxWidth:{type:Number,default:0},minCropBoxHeight:{type:Number,default:0},minContainerWidth:{type:Number,default:200},minContainerHeight:{type:Number,default:100},ready:{type:Function,default:void 0},cropstart:{type:Function,default:void 0},cropmove:{type:Function,default:void 0},cropend:{type:Function,default:void 0},crop:{type:Function,default:void 0},zoom:{type:Function,default:void 0}},setup(l,{expose:r}){const o=l,f={display:"block",maxWidth:"100%"},i=a.ref();let e;function d(){o.src?e=new u(i.value,a.toRaw(o)):e=void 0}return a.onMounted(d),a.watch(()=>o,()=>{e==null||e.destroy(),a.nextTick(d)},{deep:!0}),r({clear(){return e==null?void 0:e.clear()},crop(){return e==null?void 0:e.crop()},destroy(){return e==null?void 0:e.destroy()},disable(){return e==null?void 0:e.disable()},enable(){return e==null?void 0:e.enable()},getCanvasData(){return e==null?void 0:e.getCanvasData()},getContainerData(){return e==null?void 0:e.getContainerData()},getCropBoxData(){return e==null?void 0:e.getCropBoxData()},getCroppedCanvas(t){return e==null?void 0:e.getCroppedCanvas(t)},getData(t){return e==null?void 0:e.getData(t)},getImageData(){return e==null?void 0:e.getImageData()},move(t,n){return e==null?void 0:e.move(t,n)},moveTo(t,n){return e==null?void 0:e.moveTo(t,n)},replace(t,n){return e==null?void 0:e.replace(t,n)},reset(){return e==null?void 0:e.reset()},rotate(t){return e==null?void 0:e.rotate(t)},rotateTo(t){return e==null?void 0:e.rotateTo(t)},scale(t,n){return e==null?void 0:e.scale(t,n)},scaleX(t){return e==null?void 0:e.scaleX(t)},scaleY(t){return e==null?void 0:e.scaleY(t)},setAspectRatio(t){return e==null?void 0:e.setAspectRatio(t)},setCanvasData(t){return e==null?void 0:e.setCanvasData(t)},setCropBoxData(t){return e==null?void 0:e.setCropBoxData(t)},setData(t){return e==null?void 0:e.setData(t)},setDragMode(t){return e==null?void 0:e.setDragMode(t)},zoom(t){return e==null?void 0:e.zoom(t)},zoomTo(t,n){return e==null?void 0:e.zoomTo(t,n)},flipX(){if(e){const{scaleX:t}=e.getData();e.scaleX(-t)}},flipY(){if(e){const{scaleY:t}=e.getData();e.scaleY(-t)}}}),(t,n)=>(a.openBlock(),a.createElementBlock("div",null,[a.createElementVNode("img",{ref_key:"imageRef",ref:i,src:o.src,alt:o.alt,crossorigin:l.imgCrossOrigin,style:a.normalizeStyle([f,o.imgStyle])},null,12,s)]))}})}); 2 | -------------------------------------------------------------------------------- /dist/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue' 3 | const component: DefineComponent, Record, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /example/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require("@rushstack/eslint-patch/modern-module-resolution"); 3 | 4 | module.exports = { 5 | "root": true, 6 | "extends": [ 7 | "plugin:vue/vue3-essential", 8 | "eslint:recommended", 9 | "@vue/eslint-config-typescript/recommended", 10 | "@vue/eslint-config-prettier" 11 | ], 12 | "env": { 13 | "vue/setup-compiler-macros": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /example/.prettierignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | .local 3 | /node_modules/** 4 | 5 | /src/assets/* 6 | -------------------------------------------------------------------------------- /example/.prettierrc: -------------------------------------------------------------------------------- 1 | semi: false # 语句末尾是否加分号 2 | singleQuote: true # 使用单引号代替双引号 3 | printWidth: 100 # 超过多长进行换行 4 | trailingComma: 'none' # 多行输入的尾逗号是否添加 5 | arrowParens: 'avoid' # (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号 6 | endOfLine: 'lf' # 格式化换行符,默认值 lf 7 | # vueIndentScriptAndStyle: true # vue script 标签的缩进开启 8 | -------------------------------------------------------------------------------- /example/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /example/demo/assets/demo-d54cd5f0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballcat-projects/vue-cropper/f3b54d2cd433e2641df0b96053d497814842255f/example/demo/assets/demo-d54cd5f0.jpg -------------------------------------------------------------------------------- /example/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vue Cropper Example 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vue Cropper Example 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "run-p type-check build-only", 7 | "preview": "vite preview --port 4173", 8 | "build-only": "vite build", 9 | "type-check": "vue-tsc --noEmit", 10 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", 11 | "preinstall": "npm install ../" 12 | }, 13 | "dependencies": { 14 | "@ballcat/vue-cropper": "file:..", 15 | "ant-design-vue": "^3.2.15", 16 | "vue": "^3.2.45" 17 | }, 18 | "devDependencies": { 19 | "@rushstack/eslint-patch": "^1.2.0", 20 | "@types/node": "^18.11.17", 21 | "@vitejs/plugin-vue": "^4.0.0", 22 | "@vue/eslint-config-prettier": "^7.0.0", 23 | "@vue/eslint-config-typescript": "^11.0.2", 24 | "@vue/tsconfig": "^0.1.3", 25 | "eslint": "^8.30.0", 26 | "eslint-plugin-vue": "^9.8.0", 27 | "npm-run-all": "^4.1.5", 28 | "prettier": "^2.8.1", 29 | "typescript": "~4.9.4", 30 | "vite": "^4.0.5", 31 | "vue-tsc": "^1.0.14" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example/src/App.vue: -------------------------------------------------------------------------------- 1 | 363 | 364 | 484 | 485 | 526 | -------------------------------------------------------------------------------- /example/src/CanvasModal.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 49 | 50 | 55 | -------------------------------------------------------------------------------- /example/src/assets/app.css: -------------------------------------------------------------------------------- 1 | .header { 2 | display: flex; 3 | } 4 | 5 | .docs-demo { 6 | margin-bottom: 1rem; 7 | overflow: hidden; 8 | padding: 2px; 9 | } 10 | 11 | .img-container, 12 | .img-preview { 13 | background-color: #f7f7f7; 14 | text-align: center; 15 | width: 100%; 16 | } 17 | 18 | .img-container { 19 | max-height: 497px; 20 | min-height: 200px; 21 | } 22 | 23 | @media (min-width: 768px) { 24 | .img-container { 25 | min-height: 497px; 26 | } 27 | } 28 | 29 | .img-container > img { 30 | max-width: 100%; 31 | } 32 | 33 | .docs-preview { 34 | margin-right: -1rem; 35 | padding-top: 2px; 36 | } 37 | 38 | .img-preview { 39 | float: left; 40 | margin-bottom: 0.5rem; 41 | margin-right: 0.5rem; 42 | overflow: hidden; 43 | } 44 | 45 | .img-preview > img { 46 | max-width: 100%; 47 | } 48 | 49 | .preview-lg { 50 | height: 9rem; 51 | width: 16rem; 52 | } 53 | 54 | .preview-md { 55 | height: 4.5rem; 56 | width: 8rem; 57 | } 58 | 59 | .preview-sm { 60 | height: 2.25rem; 61 | width: 4rem; 62 | } 63 | 64 | .preview-xs { 65 | height: 1.125rem; 66 | margin-right: 0; 67 | width: 2rem; 68 | } 69 | 70 | .docs-data > .input-group { 71 | margin-bottom: 0.5rem; 72 | height: 31px; 73 | } 74 | 75 | .docs-data .ant-input-group-addon { 76 | background-color: #e9ecef; 77 | border-radius: 0.2rem; 78 | text-align: left; 79 | } 80 | 81 | .docs-data .ant-input-group-addon:first-child { 82 | min-width: 4rem; 83 | } 84 | 85 | .docs-data .ant-input-group-addon:last-child { 86 | min-width: 3rem; 87 | } 88 | 89 | .docs-buttons > .ant-btn, 90 | .docs-buttons > .ant-btn-group, 91 | .docs-buttons > .form-control { 92 | margin-bottom: 0.5rem; 93 | margin-right: 0.5rem; 94 | } 95 | 96 | .docs-buttons .ant-btn-icon-only { 97 | width: 40px; 98 | height: 40px; 99 | } 100 | 101 | .docs-buttons .ant-btn { 102 | height: 40px; 103 | } 104 | 105 | 106 | .docs-toggles > .ant-btn, 107 | .docs-toggles > .ant-radio-group, 108 | .docs-toggles > .dropdown { 109 | margin-bottom: 0.5rem; 110 | } 111 | 112 | .docs-toggles > .ant-radio-group .ant-radio-button-wrapper { 113 | padding: 0 14px; 114 | height: 40px; 115 | line-height: 38px; 116 | } 117 | 118 | .btn-group-crop > .ant-btn-primary { 119 | background: #28a745; 120 | border-color: #28a745; 121 | } 122 | .btn-group-crop > .ant-btn-primary:hover { 123 | background: #42bf5b; 124 | border-color: #42bf5b; 125 | } 126 | .btn-group-crop .ant-btn-primary:hover, .btn-group-crop .ant-btn-primary:focus { 127 | color: #fff; 128 | background: #42bf5b; 129 | border-color: #42bf5b; 130 | } 131 | .btn-group-crop .ant-btn-primary:first-child:not(:last-child) { 132 | border-right-color: #8ec59a !important; 133 | } 134 | .ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child) { 135 | border-left-color: #8ec59a !important; 136 | border-right-color: #8ec59a !important; 137 | } 138 | .btn-group-crop .ant-btn-primary:last-child:not(:first-child), .btn-group-crop .ant-btn-primary + .ant-btn-primary { 139 | border-left-color: #8ec59a !important; 140 | } 141 | 142 | 143 | .btn-secondary.ant-btn-primary { 144 | background: #6c757d; 145 | border-color: #6c757d; 146 | } 147 | .btn-secondary.ant-btn-primary:hover { 148 | background: #9397a1; 149 | border-color: #9397a1; 150 | } 151 | .btn-secondary.ant-btn-primary:hover, .btn-secondary.ant-btn-primary:focus { 152 | color: #fff; 153 | background: #9397a1; 154 | border-color: #9397a1; 155 | } 156 | -------------------------------------------------------------------------------- /example/src/assets/demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballcat-projects/vue-cropper/f3b54d2cd433e2641df0b96053d497814842255f/example/src/assets/demo.jpg -------------------------------------------------------------------------------- /example/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import 'ant-design-vue/dist/antd.css' 4 | 5 | import { 6 | Button, 7 | Row, 8 | Col, 9 | Input, 10 | Radio, 11 | Modal, 12 | Dropdown, 13 | Menu, 14 | Checkbox, 15 | Upload 16 | } from 'ant-design-vue' 17 | 18 | const app = createApp(App) 19 | 20 | app 21 | .use(Button) 22 | .use(Row) 23 | .use(Col) 24 | .use(Input) 25 | .use(Radio) 26 | .use(Modal) 27 | .use(Dropdown) 28 | .use(Menu) 29 | .use(Checkbox) 30 | .use(Upload) 31 | 32 | app.mount('#app') 33 | -------------------------------------------------------------------------------- /example/tsconfig.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.node.json", 3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | } 9 | }, 10 | 11 | "references": [ 12 | { 13 | "path": "./tsconfig.config.json" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /example/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue()], 9 | resolve: { 10 | alias: { 11 | '@': fileURLToPath(new URL('./src', import.meta.url)) 12 | } 13 | }, 14 | build: { 15 | outDir: 'demo' 16 | }, 17 | base: './' 18 | }) 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ballcat/vue-cropper", 3 | "description": "vue image cropper by cropperjs", 4 | "version": "1.0.6", 5 | "scripts": { 6 | "build": "run-p type-check build-only", 7 | "build-only": "vite build", 8 | "type-check": "vue-tsc --noEmit", 9 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" 10 | }, 11 | "files": [ 12 | "dist" 13 | ], 14 | "types": "./dist/index.d.ts", 15 | "main": "./dist/index.umd.js", 16 | "module": "./dist/index.es.js", 17 | "exports": { 18 | ".": { 19 | "import": { 20 | "types": "./dist/index.d.ts", 21 | "default": "./dist/index.es.js" 22 | }, 23 | "require": { 24 | "types": "./dist/index.d.ts", 25 | "default": "./dist/index.umd.js" 26 | } 27 | } 28 | }, 29 | "keywords": [ 30 | "cropper", 31 | "vue-cropper", 32 | "vue-cropperjs", 33 | "cropperjs" 34 | ], 35 | "author": "Hccake ", 36 | "license": "MIT", 37 | "dependencies": { 38 | "cropperjs": "^1.5.13" 39 | }, 40 | "peerDependencies": { 41 | "vue": ">=3.2.0" 42 | }, 43 | "devDependencies": { 44 | "@rushstack/eslint-patch": "^1.2.0", 45 | "@types/node": "^18.11.17", 46 | "@vitejs/plugin-vue": "^4.0.0", 47 | "@vue/eslint-config-prettier": "^7.0.0", 48 | "@vue/eslint-config-typescript": "^11.0.2", 49 | "@vue/tsconfig": "^0.1.3", 50 | "eslint": "^8.30.0", 51 | "eslint-plugin-vue": "^9.8.0", 52 | "npm-run-all": "^4.1.5", 53 | "prettier": "^2.8.1", 54 | "typescript": "~4.9.4", 55 | "vite": "^4.0.5", 56 | "vite-plugin-dts": "^1.7.1", 57 | "vue": "^3.2.45", 58 | "vue-tsc": "^1.0.14" 59 | }, 60 | "engines": { 61 | "node": ">=12.0.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/VueCropper.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 470 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | 3 | import VueCropper from './VueCropper.vue' 4 | export default VueCropper 5 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue' 3 | const component: DefineComponent, Record, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type Cropper from 'cropperjs' 2 | 3 | export interface VueCropperInstance { 4 | clear(): Cropper 5 | crop(): Cropper 6 | destroy(): Cropper 7 | disable(): Cropper 8 | enable(): Cropper 9 | getCanvasData(): Cropper.CanvasData 10 | getContainerData(): Cropper.ContainerData 11 | getCropBoxData(): Cropper.CropBoxData 12 | getCroppedCanvas(options?: Cropper.GetCroppedCanvasOptions): HTMLCanvasElement 13 | getData(rounded?: boolean): Cropper.Data 14 | getImageData(): Cropper.ImageData 15 | move(offsetX: number, offsetY?: number): Cropper 16 | moveTo(x: number, y?: number): Cropper 17 | replace(url: string, onlyColorChanged?: boolean): Cropper 18 | reset(): Cropper 19 | rotate(degree: number): Cropper 20 | rotateTo(degree: number): Cropper 21 | scale(scaleX: number, scaleY?: number): Cropper 22 | scaleX(scaleX: number): Cropper 23 | scaleY(scaleY: number): Cropper 24 | setAspectRatio(aspectRatio: number): Cropper 25 | setCanvasData(data: Cropper.SetCanvasDataOptions): Cropper 26 | setCropBoxData(data: Cropper.SetCropBoxDataOptions): Cropper 27 | setData(data: Cropper.SetDataOptions): Cropper 28 | setDragMode(dragMode: Cropper.DragMode): Cropper 29 | zoom(ratio: number): Cropper 30 | zoomTo(ratio: number, pivot?: { x: number; y: number }): Cropper 31 | 32 | flipX(): void 33 | flipY(): void 34 | } 35 | -------------------------------------------------------------------------------- /tsconfig.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.node.json", 3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | } 9 | }, 10 | 11 | "references": [ 12 | { 13 | "path": "./tsconfig.config.json" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'url' 2 | import { resolve } from 'path' 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import dts from 'vite-plugin-dts' 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | plugins: [ 10 | dts({ 11 | staticImport: true, 12 | // skipDiagnostics: false, 13 | // logDiagnostics: true, 14 | insertTypesEntry: true, 15 | rollupTypes: true 16 | }), 17 | vue() 18 | ], 19 | resolve: { 20 | alias: { 21 | '@': fileURLToPath(new URL('./src', import.meta.url)) 22 | } 23 | }, 24 | build: { 25 | lib: { 26 | entry: resolve(__dirname, 'src/index.ts'), 27 | name: 'VueCropper', 28 | fileName: format => `index.${format}.js` 29 | }, 30 | rollupOptions: { 31 | // 确保外部化处理那些你不想打包进库的依赖 32 | external: ['vue', 'cropperjs'], 33 | output: { 34 | // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量 35 | globals: { 36 | vue: 'Vue', 37 | cropperjs: 'CropperJs' 38 | } 39 | } 40 | } 41 | } 42 | }) 43 | --------------------------------------------------------------------------------