├── env.d.ts ├── src ├── App.scss ├── assets │ └── references │ │ ├── index.scss │ │ ├── _colors.scss │ │ └── _mixins.scss ├── components │ ├── index.ts │ ├── imageEditor.ts │ ├── ImageEditor.scss │ └── ImageEditor.vue ├── App.vue ├── main.ts └── ViewerPlugin.ts ├── .vscode └── extensions.json ├── .prettierrc.json ├── tsconfig.app.json ├── index.html ├── .eslintrc.cjs ├── tsconfig.node.json ├── .gitignore ├── tsconfig.json ├── vite.config.ts ├── package.json └── README.md /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/App.scss: -------------------------------------------------------------------------------- 1 | @import "./assets/references/index.scss"; 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/references/index.scss: -------------------------------------------------------------------------------- 1 | @import "./colors"; 2 | @import "./mixins"; 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ImageEditor } from "./ImageEditor.vue" 2 | export type { ImageEditorProps } from "./imageEditor" -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "semi": false, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "printWidth": 100, 7 | "trailingComma": "none" 8 | } -------------------------------------------------------------------------------- /src/components/imageEditor.ts: -------------------------------------------------------------------------------- 1 | export interface ImageEditorProps { 2 | fileImage?: File; 3 | colorBrush: string, 4 | borderCropDivColor: string, 5 | backgroundCropDivColor: string, 6 | maxHeight? : number 7 | maxWidth? : number 8 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import "@/assets/references/index.scss"; 4 | import ViewerPlugin from './ViewerPlugin'; 5 | 6 | const app = createApp(App) 7 | app.use(ViewerPlugin); 8 | app.mount('#app') 9 | -------------------------------------------------------------------------------- /src/ViewerPlugin.ts: -------------------------------------------------------------------------------- 1 | import type { App } from "vue"; 2 | import { ImageEditor } from "./components"; 3 | 4 | 5 | export default { 6 | install: (app: App) => { 7 | app.component("ImageEditor", ImageEditor); 8 | }, 9 | }; 10 | 11 | export { ImageEditor }; 12 | -------------------------------------------------------------------------------- /src/assets/references/_colors.scss: -------------------------------------------------------------------------------- 1 | @use "sass:selector"; 2 | 3 | //Global Color 4 | $white: #fff; 5 | $gray:#595959; 6 | $black: #000; 7 | $orange: #db9300; 8 | $light-orange: #f0ddb8; 9 | $bubble-message-owner: #f9f3dd; 10 | $bubble-message: #f2f3f5; 11 | 12 | //Global Variables 13 | $font-family: "Shabnam"; 14 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "exclude": ["src/**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "baseUrl": ".", 8 | "paths": { 9 | "@/*": ["./src/*"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Image Editor Vue3 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.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', 10 | '@vue/eslint-config-prettier/skip-formatting' 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 'latest' 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18/tsconfig.json", 3 | "include": [ 4 | "vite.config.*", 5 | "vitest.config.*", 6 | "cypress.config.*", 7 | "nightwatch.conf.*", 8 | "playwright.config.*" 9 | ], 10 | "compilerOptions": { 11 | "composite": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Bundler", 14 | "types": ["node"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | } 10 | ], 11 | "include": [ 12 | "env.d.ts", 13 | "src/**/*", 14 | "src/**/*.vue" 15 | ], 16 | "compilerOptions": { 17 | "strictNullChecks": false, 18 | "baseUrl": ".", 19 | "paths": { 20 | "@/*": [ 21 | "./src/*" 22 | ] 23 | } 24 | }, 25 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from "url"; 2 | 3 | import { defineConfig } from "vite"; 4 | 5 | import vue from "@vitejs/plugin-vue"; 6 | import typescript2 from "rollup-plugin-typescript2"; 7 | 8 | // https://vitejs.dev/config/ 9 | export default defineConfig({ 10 | plugins: [ 11 | vue(), 12 | typescript2({ 13 | check: false, 14 | include: ["src/components/*.vue"], 15 | tsconfigOverride: { 16 | compilerOptions: { 17 | sourceMap: true, 18 | declaration: true, 19 | declarationMap: true, 20 | }, 21 | exclude: ["vite.config.ts", "main.ts"], 22 | }, 23 | }), 24 | ], 25 | resolve: { 26 | alias: { 27 | "@": fileURLToPath(new URL("./src", import.meta.url)), 28 | }, 29 | }, 30 | build: { 31 | cssCodeSplit: false, 32 | lib: { 33 | entry: "./src/ViewerPlugin.ts", 34 | formats: ["es", "cjs"], 35 | name: "ViewerPlugin", 36 | fileName: (format) => (format === "es" ? "index.js" : "index.cjs"), 37 | }, 38 | rollupOptions: { 39 | external: ["vue"], 40 | output: { 41 | globals: { 42 | vue: "Vue", 43 | }, 44 | }, 45 | }, 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /src/assets/references/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin main-size-xs { 2 | @media (max-width: 575.98px) { 3 | @content; 4 | } 5 | } 6 | 7 | @mixin main-size-sm { 8 | @media (min-width: 575.98px) { 9 | @content; 10 | } 11 | } 12 | 13 | @mixin main-size-md { 14 | @media (min-width: 767.98px) { 15 | @content; 16 | } 17 | } 18 | 19 | @mixin main-size-xl { 20 | @media (min-width: 1199.98px) { 21 | @content; 22 | } 23 | } 24 | 25 | @mixin responsive-more-size( 26 | $property, 27 | $desktop-value, 28 | $laptop-value, 29 | $tablet-value, 30 | $mobile-value 31 | ) { 32 | @include main-size-xs { 33 | // 576 - 320 34 | #{$property}: $mobile-value; 35 | } 36 | 37 | @include main-size-sm { 38 | // 768 - 576 39 | 40 | #{$property}: $tablet-value; 41 | } 42 | @include main-size-md { 43 | //1200 - 768 44 | 45 | #{$property}: $laptop-value; 46 | 47 | } 48 | @include main-size-xl { 49 | //higher than 1200 50 | #{$property}: $desktop-value; 51 | } 52 | } 53 | 54 | @mixin secondary-text-style($font-family, $font-style, $font-weight , $color) { 55 | font-family: $font-family; 56 | font-style: $font-style; 57 | font-weight: $font-weight; 58 | color: $color; 59 | transition: 0.5s; 60 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-image-editor", 3 | "version": "1.1.1", 4 | "description": "An Image Editor component library", 5 | "type": "module", 6 | "main": "dist/index.cjs", 7 | "module": "dist/index.js", 8 | "exports": { 9 | ".": "./dist/index.js", 10 | "./styles.css": "./dist/style.css" 11 | }, 12 | "browser": { 13 | "./styles.css": "./dist/style.css" 14 | }, 15 | "types": "dist/ViewerPlugin.d.ts", 16 | "files": [ 17 | "dist" 18 | ], 19 | "scripts": { 20 | "dev": "vite", 21 | "build": "run-p type-check build-only", 22 | "preview": "vite preview", 23 | "build-only": "vite build", 24 | "type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false", 25 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", 26 | "format": "prettier --write src/" 27 | }, 28 | "keywords": [ 29 | "vue", 30 | "vue3", 31 | "component", 32 | "library", 33 | "image", 34 | "edit", 35 | "typescript" 36 | ], 37 | "repository": { 38 | "type": "git", 39 | "url": "https://github.com/alireza013013/vue-image-editor" 40 | }, 41 | "dependencies": { 42 | "compressorjs": "^1.2.1", 43 | "path": "^0.12.7", 44 | "vue": "^3.3.4" 45 | }, 46 | "devDependencies": { 47 | "@rushstack/eslint-patch": "^1.3.2", 48 | "@tsconfig/node18": "^18.2.0", 49 | "@types/node": "^18.17.0", 50 | "@vitejs/plugin-vue": "^4.2.3", 51 | "@vue/eslint-config-prettier": "^8.0.0", 52 | "@vue/eslint-config-typescript": "^11.0.3", 53 | "@vue/tsconfig": "^0.4.0", 54 | "eslint": "^8.45.0", 55 | "eslint-plugin-vue": "^9.15.1", 56 | "npm-run-all": "^4.1.5", 57 | "prettier": "^3.0.0", 58 | "rollup-plugin-typescript2": "^0.35.0", 59 | "sass": "^1.64.1", 60 | "sass-loader": "^13.3.2", 61 | "typescript": "~5.1.6", 62 | "vite": "^4.4.6", 63 | "vue-tsc": "^1.8.6" 64 | } 65 | } -------------------------------------------------------------------------------- /src/components/ImageEditor.scss: -------------------------------------------------------------------------------- 1 | @import "../assets/references/index.scss"; 2 | 3 | .main-editor-div { 4 | @include responsive-more-size(padding, 36px, 36px, 10px, 10px); 5 | display: flex; 6 | flex-direction: column; 7 | margin: auto; 8 | background-color: var(--modal-bg); 9 | @include responsive-more-size(border-radius, 10px, 10px, 0, 0); 10 | overflow-y: auto; 11 | -ms-overflow-style: none; /* IE and Edge */ 12 | scrollbar-width: none; /* Firefox */ 13 | position: relative; 14 | 15 | &::-webkit-scrollbar { 16 | display: none; 17 | } 18 | 19 | align-items: center; 20 | 21 | .blur-div { 22 | display: flex; 23 | justify-content: center; 24 | align-items: center; 25 | background: #dadada; 26 | border-radius: 4px; 27 | 28 | .canvas-resize-div { 29 | display: flex; 30 | flex-direction: column; 31 | position: relative; 32 | direction: ltr !important; 33 | max-width: fit-content; 34 | max-height: fit-content; 35 | max-width: -moz-fit-content; 36 | max-height: -moz-fit-content; 37 | 38 | .canvas-style { 39 | width: 100%; 40 | height: 100%; 41 | } 42 | 43 | .resizable { 44 | background: transparent; 45 | width: 100px; 46 | height: 100px; 47 | position: absolute; 48 | 49 | .resizers { 50 | width: 100%; 51 | height: 100%; 52 | box-sizing: border-box; 53 | 54 | .resizer { 55 | width: 10px; 56 | height: 10px; 57 | border-radius: 50%; 58 | position: absolute; 59 | } 60 | 61 | .top-left { 62 | left: -5px; 63 | top: -5px; 64 | cursor: nwse-resize; 65 | } 66 | 67 | .top-right { 68 | right: -5px; 69 | top: -5px; 70 | cursor: nesw-resize; 71 | } 72 | 73 | .bottom-left { 74 | left: -5px; 75 | bottom: -5px; 76 | cursor: nesw-resize; 77 | } 78 | 79 | .bottom-right { 80 | right: -5px; 81 | bottom: -5px; 82 | cursor: nwse-resize; 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | .show-pre-div { 90 | margin: 20px auto; 91 | max-width: 300px; 92 | display: flex; 93 | flex-direction: column; 94 | position: absolute; 95 | top: -1000000px; 96 | 97 | canvas { 98 | border-radius: 8px; 99 | } 100 | } 101 | 102 | .title-div { 103 | @include responsive-more-size(margin-bottom, 36px, 36px, 30px, 30px); 104 | } 105 | 106 | .options-div { 107 | width: 100%; 108 | display: flex; 109 | column-gap: 10px; 110 | height: 40px; 111 | @include responsive-more-size(margin-top, 36px, 36px, 30px, 30px); 112 | 113 | .option { 114 | display: flex; 115 | cursor: pointer; 116 | 117 | .icon-option { 118 | width: 30px; 119 | height: 30px; 120 | } 121 | 122 | .active { 123 | filter: invert(69%) sepia(85%) saturate(2633%) hue-rotate(4deg) brightness(90%) contrast(101%); 124 | } 125 | } 126 | } 127 | 128 | .options-paiting { 129 | width: 100%; 130 | margin-top: 20px; 131 | 132 | input[type="color"] { 133 | appearance: none; 134 | -moz-appearance: none; 135 | -webkit-appearance: none; 136 | background: none; 137 | border: 0; 138 | cursor: pointer; 139 | height: 30px; 140 | padding: 0; 141 | width: 30px; 142 | } 143 | 144 | *:focus { 145 | border-radius: 0; 146 | outline: none; 147 | } 148 | 149 | ::-webkit-color-swatch-wrapper { 150 | padding: 0; 151 | } 152 | 153 | ::-webkit-color-swatch { 154 | border: 0; 155 | border-radius: 50%; 156 | } 157 | 158 | ::-moz-color-swatch, 159 | ::-moz-focus-inner { 160 | border: 0; 161 | } 162 | 163 | ::-moz-focus-inner { 164 | padding: 0; 165 | } 166 | } 167 | 168 | .submit-button-div { 169 | width: 100%; 170 | display: flex; 171 | column-gap: 6px; 172 | margin-top: 12px; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 Image Editor 2 | 3 | Modern lightweight Vue 3 image editor component 4 | 5 |

6 | npm 7 |

8 | 9 | ## Demo 10 | https://alireza013013.github.io/example-vue-image-editor/ 11 | 12 | ## Features 13 | 14 | - Component user interface customization 15 | - Photo cropping 16 | - Painting On Image 17 | - No reduction in image quality 18 | - Image compression to reduce size without losing quality 19 | - Select desired width and height 20 | 21 | ## Getting started 22 | 23 | ### Installation 24 | 25 | The first step is to install using `npm`: 26 | 27 | ```bash 28 | npm i vue3-image-editor 29 | ``` 30 | 31 | ### Basic Using 32 | 33 | #### Global Using 34 | you should import ViewPlugin And css file in the `main.ts` and use ViewPlugin. 35 | 36 | ```ts 37 | import ViewerPlugin from "vue3-image-editor" 38 | import "vue3-image-editor/styles.css" 39 | 40 | app.use(ViewerPlugin) 41 | ``` 42 | 43 | #### Local Using 44 | You must import the component and css file in the desired file as follows. 45 | 46 | ```ts 47 | import { ImageEditor } from "vue3-image-editor" 48 | import "vue3-image-editor/styles.css" 49 | ``` 50 | 51 | ### Basic Example 52 | Simple usage is given in the following example. 53 | 54 | ```vue 55 | 81 | 82 | 128 | ``` 129 | To access component functions, a variable must be defined and connected to the component through ref. In the above example, `imageEditor` variable is used, whose initial value is null. You have to put your photo file in a `selectedFileImageForEdit` variable so that the photo will be displayed for you. 130 | To change the blob to a file, you can do the following. 131 | 132 | ```ts 133 | const finalFile: File = new File([blob], "nameFile", { type: "image/png" }) 134 | ``` 135 | 136 | 137 | ### Crop Feature 138 | To use the photo cropping function, you must call `enableCroping`. For this purpose, you can define a button as in the example above and call `enableCroping` on the @click function button. 139 | After calling `enableCroping`, the photo cropping square will appear. Now, to save the changes, you can call the `saveChanges` and to cancel the changes, you can call the `cancelChanges`. 140 | You can change the square appearance of the photo by using `background-crop-div-color` and `border-crop-div-color`. 141 | 142 | #### Notice 143 | When the function of cutting and drawing is not yet activated, it is better not to show the buttons related to `saveChanges` and `cancelChanges` functions to the user so that the user cannot call them, because there is no need for this. 144 | When one of the functions is active, it is better not to show the other. 145 | 146 | ### Paiting Feature 147 | To use the feature of drawing on the image, you must call `enablePainting`. For this purpose, you can define a button as in the example above and call `enablePainting` on the @click function button. 148 | Now, to save the changes, you can call the `saveChanges` and to cancel the changes, you can call the `cancelChanges`. 149 | The default color of the pen is black. To change it, you can change `color-brush` variable. You can also put a input color like the example above so that the user can choose the color himself. 150 | 151 | ### Width And Height 152 | You can specify the maximum width and maximum height of the photo. In the absence of these values, the photo will be loaded in its actual size. The recommended amount for these two is between 400 and 500. 153 | 154 | ### Finish Editing 155 | After finishing the changes on the image, to access the final file, you must call `finishEditing`. Then you have to rewrite the `finishEditImage` function because this function returns the final file to us. These things can be seen in the above example. 156 | -------------------------------------------------------------------------------- /src/components/ImageEditor.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 533 | 534 | --------------------------------------------------------------------------------