├── 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 |
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 |
56 |
58 |
59 |
60 |
61 | Painting
62 |
63 |
64 | Crop
65 |
66 |
67 | Download
68 |
69 |
70 | Finish
71 |
72 |
73 |
74 |
75 |
76 |
77 | Cancel
78 | Save
79 |
80 |
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 |
2 |
3 |
4 |
5 |
11 |
12 |
15 |
20 |
25 |
30 |
31 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
533 |
534 |
--------------------------------------------------------------------------------