├── .gitignore ├── demo ├── .browserslistrc ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── assets │ │ └── img │ │ │ ├── 8s.jpg │ │ │ └── placeholder.png │ ├── components │ │ └── TextBox.vue │ └── main.js └── vue.config.js ├── images └── demo.jpg ├── package-lock.json ├── package.json ├── readme.md └── src └── drr.vue /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | *.tgz 4 | .nyc_output 5 | coverage 6 | node_modules -------------------------------------------------------------------------------- /demo/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # demo 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | npm run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /demo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build" 8 | }, 9 | "dependencies": { 10 | "@minogin/vue-drag-resize-rotate": "^1.0.3", 11 | "vue": "^2.5.22" 12 | }, 13 | "devDependencies": { 14 | "@vue/cli-plugin-babel": "^3.3.0", 15 | "@vue/cli-service": "^3.3.0", 16 | "vue-template-compiler": "^2.5.21" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minogin/vue-drag-resize-rotate/d95c49bb9f2e4395d1e3a09b66621074e0b1a077/demo/public/favicon.ico -------------------------------------------------------------------------------- /demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | demo 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demo/src/App.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 49 | 50 | 64 | -------------------------------------------------------------------------------- /demo/src/assets/img/8s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minogin/vue-drag-resize-rotate/d95c49bb9f2e4395d1e3a09b66621074e0b1a077/demo/src/assets/img/8s.jpg -------------------------------------------------------------------------------- /demo/src/assets/img/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minogin/vue-drag-resize-rotate/d95c49bb9f2e4395d1e3a09b66621074e0b1a077/demo/src/assets/img/placeholder.png -------------------------------------------------------------------------------- /demo/src/components/TextBox.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 36 | 37 | 50 | -------------------------------------------------------------------------------- /demo/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import drr from '@minogin/vue-drag-resize-rotate' 4 | 5 | Vue.config.productionTip = false 6 | 7 | Vue.component('drr', drr) 8 | 9 | new Vue({ 10 | render: h => h(App), 11 | }).$mount('#app') 12 | -------------------------------------------------------------------------------- /demo/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devServer: { 3 | port: 80 4 | } 5 | } -------------------------------------------------------------------------------- /images/demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minogin/vue-drag-resize-rotate/d95c49bb9f2e4395d1e3a09b66621074e0b1a077/images/demo.jpg -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@minogin/vue-drag-resize-rotate", 3 | "version": "1.0.5", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@minogin/vector": { 8 | "version": "1.0.2", 9 | "resolved": "https://registry.npmjs.org/@minogin/vector/-/vector-1.0.2.tgz", 10 | "integrity": "sha512-iMlPme16/YbFmtcofRJS0RU17U7PL4pwIkEnCZ6heNGfQOwd7lMSgXUnZavI5SE4A3bllVISv99+KxNbOMbAow==" 11 | }, 12 | "vue": { 13 | "version": "2.5.22", 14 | "resolved": "https://registry.npmjs.org/vue/-/vue-2.5.22.tgz", 15 | "integrity": "sha512-pxY3ZHlXNJMFQbkjEgGVMaMMkSV1ONpz+4qB55kZuJzyJOhn6MSy/YZdzhdnumegNzVTL/Dn3Pp4UrVBYt1j/g==" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@minogin/vue-drag-resize-rotate", 3 | "version": "1.0.5", 4 | "description": "Vue component which acts as a draggable, resizable and rotateable container for any content.", 5 | "keywords": [ 6 | "vue", 7 | "drag", 8 | "resize", 9 | "rotate", 10 | "draggable", 11 | "resizable", 12 | "rotateable" 13 | ], 14 | "author": { 15 | "name": "Andrey Minogin", 16 | "email": "minogin@gmail.com" 17 | }, 18 | "license": "ISC", 19 | "main": "src/drr.vue", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/minogin/vue-drag-resize-rotate" 23 | }, 24 | "scripts": { 25 | "test": "echo \"Error: no test specified\" && exit 1" 26 | }, 27 | "dependencies": { 28 | "@minogin/vector": "^1.0.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Description 2 | Vue component which acts as a draggable, resizable and rotateable container for any content. 3 | 4 | [![Demo](./images/demo.jpg)](http://drr.minogin.com/) 5 | 6 | ### Demo 7 | 8 | http://drr.minogin.com/ 9 | 10 | ### Features 11 | * All the properties are reactive. 12 | * Correct rotation based on vector geometry. Rotated container resizes correctly. 13 | * Supports fixed aspect ratio which is applied correctly when container is rotated. 14 | * Supports active (e.g. editable) content. For example you can create a draggable and resizable textbox and edit the text inside on double click. 15 | * Supports both outer and inner boundaries, i.e. container will always contain inner boundary and outer boundary will always contain the container. 16 | * DRR-containers could be nested, e.g. for editing tree-like structures. 17 | 18 | ### Please note! 19 | 20 | (x; y) are the coordinates of the **center** of the container, not the left-top corner. It is because the container could be rotated and therefore we don't know which corner would be left-top. 21 | 22 | ### Inspired by 23 | 24 | [Vue-drag-resize component](https://www.npmjs.com/package/vue-drag-resize) 25 | 26 | 27 | 28 | # Installation and usage 29 | 30 | Install with npm 31 | 32 | ```bash 33 | npm i @minogin/vue-drag-resize-rotate 34 | ``` 35 | 36 | Add the following lines to your index.js (main.js): 37 | 38 | ```javascript 39 | import drr from '@minogin/vue-drag-resize-rotate' 40 | 41 | ... 42 | 43 | Vue.component('drr', drr) 44 | ``` 45 | 46 | Use DRR in your Vue templates: 47 | 48 | ```javascript 49 | 58 | 59 | 60 | ``` 61 | 62 | ```javascript 63 | 80 | 81 | 82 | ``` 83 | 84 | # Documentation 85 | 86 | ## Properties 87 | 88 | #### x, y 89 | Type: `Number`\ 90 | Required: `true` 91 | 92 | Center of the container. 93 | 94 | **Please note!**\ 95 | (x; y) are the coordinates of the **center** of the container, not the left-top corner. It is because the container could be rotated and therefore we don't know which corner would be left-top. 96 | 97 | #### w, h 98 | Type: `Number`\ 99 | Required: `true` 100 | 101 | Width and height of the container. 102 | 103 | #### angle 104 | Type: `Number`\ 105 | Required: `false`\ 106 | Default: `0` 107 | 108 | Rotation angle in degrees starting from up-axis. 109 | 110 | #### selected 111 | Type: `Boolean`\ 112 | Required: `false`\ 113 | Default: `false` 114 | 115 | Whether the container is selected and therefore able to be dragged, resized and rotated. 116 | 117 | #### selectable 118 | Type: `Boolean`\ 119 | Required: `false`\ 120 | Default: `true` 121 | 122 | Whether the container could be selected by mouse click or touch. 123 | 124 | #### draggable 125 | Type: `Boolean`\ 126 | Required: `false`\ 127 | Default: `true` 128 | 129 | Whether the container could be dragged. 130 | 131 | #### resizable 132 | Type: `Boolean`\ 133 | Required: `false`\ 134 | Default: `true` 135 | 136 | Whether the container could be resized. 137 | 138 | #### rotatable 139 | Type: `Boolean`\ 140 | Required: `false`\ 141 | Default: `true` 142 | 143 | Whether the container could be rotated. 144 | 145 | #### aspectRatio 146 | Type: `Boolean`\ 147 | Required: `false`\ 148 | Default: `false` 149 | 150 | Whether to preserve aspect ratio on resize. 151 | 152 | #### hasActiveContent 153 | Type: `Boolean`\ 154 | Required: `false`\ 155 | Default: `false` 156 | 157 | Setting this to `true` means that there is some content in the container that could be activated by double click. For example editable text field, an image which enlarges or some active nested elements. 158 | When content is activated by double click the container gets locked and every child receives 'active' event. 159 | ```javascript 160 | // In child component: 161 | mounted() { 162 | this.$on('active', this.onActive) 163 | }, 164 | ``` 165 | 166 | The container itself doesn't roll back to normal state on blur. The children must determine by themselves when the job is finished and send the 'content-inactive' event to the parent DRR like this: 167 | ```javascript 168 | // In child component: 169 | this.$parent.$emit('content-inactive') 170 | ``` 171 | 172 | 173 | #### outerBound 174 | Type: `Object`\ 175 | Required: `false`\ 176 | Default: `null` 177 | 178 | A rectangular object 179 | ```javascript 180 | { 181 | x: ..., 182 | y: ..., 183 | w: ..., 184 | h: ... 185 | } 186 | ``` 187 | which is the outer limit for DRR dragging and resizing. This boundary currently applies only to not rotated DRR. x and y are the coordinates of the center of the rectangle. 188 | 189 | #### innerBound 190 | Type: `Object`\ 191 | Required: `false`\ 192 | Default: `null` 193 | 194 | A rectangular object 195 | ```javascript 196 | { 197 | x: ..., 198 | y: ..., 199 | w: ..., 200 | h: ... 201 | } 202 | ``` 203 | which is the inner limit for DRR dragging and resizing. It means that this rectangle will always be contained inside the container. 204 | This boundary currently applies only to not rotated DRR. x and y are the coordinates of the center of the rectangle. 205 | 206 | ## Events 207 | 208 | #### select 209 | Fires when container is selected (focused). 210 | 211 | #### deselect 212 | Fires when container loses focus by clicking outside of the container. 213 | 214 | **Please note!** Currently clicking on another DRRs does not deselect current DRR. You must keep proper selection yourself using `selected` property. 215 | 216 | #### dragstart (rect) 217 | Properties:\ 218 | `rect`: `Object { x, y, w, h, angle }` - container coordinates on drag start 219 | 220 | Fires on drag start. 221 | 222 | #### drag (rect) 223 | Properties:\ 224 | `rect`: `Object { x, y, w, h, angle }` - current container coordinates 225 | 226 | Fires continuously while dragging 227 | 228 | **Please note!** This event fires many times per second. Consider handler [debouncing](https://lodash.com/docs/#debounce). 229 | 230 | #### dragstop (rect, startRect) 231 | Properties:\ 232 | `rect`: `Object { x, y, w, h, angle }` - final container coordinates\ 233 | `startRect`: `Object { x, y, w, h, angle }` - initial container coordinates. 234 | 235 | Fires when drag finishes. 236 | 237 | #### resizestart (rect) 238 | Properties:\ 239 | `rect`: `Object { x, y, w, h, angle }` - container coordinates on resize start. 240 | 241 | Fires on resize start. 242 | 243 | #### resize (rect) 244 | Properties:\ 245 | `rect`: `Object { x, y, w, h, angle }` - current container coordinates 246 | 247 | Fires continuously while resizing. 248 | 249 | **Please note!** This event fires many times per second. Consider handler [debouncing](https://lodash.com/docs/#debounce). 250 | 251 | #### resizestop (rect, startRect) 252 | Properties:\ 253 | `rect`: `Object { x, y, w, h, angle }` - final container coordinates\ 254 | `startRect`: `Object { x, y, w, h, angle }` - initial container coordinates 255 | 256 | Fires when resize finishes. 257 | 258 | #### rotatestart (rect) 259 | Properties:\ 260 | `rect`: `Object { x, y, w, h, angle }` - container coordinates on rotation start. 261 | 262 | Fires on rotation start. 263 | 264 | #### rotate (rect) 265 | Properties:\ 266 | `rect`: `Object { x, y, w, h, angle }` - current container coordinates 267 | 268 | Fires continuously while rotating. 269 | 270 | **Please note!** This event fires many times per second. Consider handler [debouncing](https://lodash.com/docs/#debounce). 271 | 272 | #### rotatestop (rect, startRect) 273 | Properties:\ 274 | `rect`: `Object { x, y, w, h, angle }` - final container coordinates\ 275 | `startRect`: `Object { x, y, w, h, angle }` - initial container coordinates 276 | 277 | Fires when rotation finishes. 278 | 279 | #### change (rect) 280 | Properties:\ 281 | `rect`: `Object { x, y, w, h, angle }` - final container coordinates\ 282 | 283 | Fired when drag, resize or rotate finishes. 284 | 285 | #### content-active 286 | 287 | Fired when container is double-clicked and `hasActiveContent` property is set to `true`. See `hasActiveContent` property. 288 | 289 | #### active 290 | 291 | Fired on every child component when container is double-clicked and `hasActiveContent` property is set to `true`. See `hasActiveContent` property. 292 | 293 | #### inactive 294 | 295 | Fired on every child when container receives 'content-inactive' event and `hasActiveContent` property is set to `true`. Might be useful to inactivate multiple children. See `hasActiveContent` property. 296 | 297 | ## Control events 298 | 299 | #### content-inactive 300 | 301 | When container being in 'active content' state receives this event it returns to normal state. This event must be fired by children when content job is finished. See `hasActiveContent` property. 302 | 303 | ## Hints 304 | 305 | For the content (img, div, etc.) to resize along with the container use 306 | ```css 307 | width: 100%; 308 | height: 100%; 309 | ``` 310 | style on the content element. 311 | 312 | For nested text inputs use 313 | ```css 314 | position: absolute; 315 | ``` 316 | 317 | 318 | # Repository 319 | 320 | https://github.com/minogin/vue-drag-resize-rotate 321 | 322 | # Contacts 323 | 324 | Please contact me at [minogin@gmail.com](mailto:minogin@gmail.com) or at GitHub 325 | 326 | # License 327 | 328 | [ISC](https://opensource.org/licenses/ISC) 329 | -------------------------------------------------------------------------------- /src/drr.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 684 | 685 | 786 | --------------------------------------------------------------------------------