├── .babelrc ├── .github └── FUNDING.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── config ├── dev.conf.js ├── index.js ├── prod.conf.js └── test.conf.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── components │ ├── vue-drag-resize.css │ ├── vue-drag-resize.html │ ├── vue-drag-resize.js │ └── vue-drag-resize.vue ├── demo │ ├── app.js │ ├── app.vue │ ├── components │ │ └── toolbar │ │ │ ├── toolbar.css │ │ │ ├── toolbar.html │ │ │ ├── toolbar.js │ │ │ └── toolbar.vue │ ├── icons │ │ ├── index.js │ │ ├── lock.js │ │ ├── toBottom.js │ │ └── toTop.js │ └── store │ │ ├── index.js │ │ └── modules │ │ └── rect │ │ ├── actions.js │ │ ├── getters.js │ │ ├── index.js │ │ ├── mutation-types.js │ │ ├── mutations.js │ │ └── state.js └── index.js ├── static └── index.html └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": ["@babel/plugin-proposal-class-properties"] 4 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: vue-drag-resize -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | .idea/ 4 | npm-debug.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/demo.js 4 | static/ 5 | npm-debug.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Maurizio Bonani 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 |

logo

2 |

Vue-drag-resize

3 | 4 |

5 | 6 | [![Latest Version on NPM](https://img.shields.io/npm/v/vue-drag-resize.svg?style=flat-square)](https://npmjs.com/package/vue-drag-resize) 7 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 8 | [![npm](https://img.shields.io/npm/dt/vue-drag-resize.svg?style=flat-square)](https://www.npmjs.com/package/vue-drag-resize) 9 |

10 | 11 |

12 | 13 | 14 | 15 |

16 | 17 | > Vue Component for draggable and resizable elements. 18 | 19 | ## Table of Contents 20 | 21 | * [Features](#features) 22 | * [Install and basic usage](#install-and-basic-usage) 23 | * [Props](#props) 24 | * [Events](#events) 25 | * [Contributing](#contributing) 26 | * [License](#license) 27 | 28 | ### Demo 29 | 30 | [Demo](http://kirillmurashov.com/vue-drag-resize) 31 | 32 | ### Features 33 | 34 | * A lightweight, no-dependency 35 | * All props are reactive 36 | * Support touch events 37 | * Snap element to custom grid 38 | * Use draggable, resizable or both 39 | * Define sticks for resizing 40 | * Save aspect ratio for resizable components 41 | * Restrict size and movement to parent element 42 | * Restrict drag to vertical or horizontal axis 43 | 44 | ## Install and basic usage 45 | 46 | ```bash 47 | $ npm i -s vue-drag-resize 48 | ``` 49 | 50 | 51 | Register the component: 52 | 53 | ```js 54 | import Vue from 'vue' 55 | import VueDragResize from 'vue-drag-resize' 56 | 57 | Vue.component('vue-drag-resize', VueDragResize) 58 | ``` 59 | 60 | Use the component: 61 | 62 | ```vue 63 | 72 | 73 | 102 | ``` 103 | 104 | ### Props 105 | 106 | #### isActive 107 | Type: `Boolean`
108 | Required: `false`
109 | Default: `false` 110 | 111 | Determines whether the component should be active. 112 | 113 | 确定组件是否应处于活动状态。 114 | 115 | ```html 116 | 117 | ``` 118 | 119 | #### preventActiveBehavior 120 | Type: `Boolean`
121 | Required: `false`
122 | Default: `false` 123 | 124 | Disable behavior of the component by clicking on it and clicking outside the component's area (isActive: true / false).
125 | If the prop is enabled, the component is oriented only to the specified. 126 | 127 | 通过单击组件并单击组件区域外部来禁用组件的行为(isActive:true / false)。
128 | 如果启用了prop,则组件仅面向指定的。 129 | 130 | ```html 131 | 132 | ``` 133 | 134 | #### parentW 135 | Type: `Number`
136 | Required: `false`
137 | Default: `0` 138 | 139 | Define the initial width of the parent element. If not specified it calculated automatically.
140 | With this parameter, you can set the bounding area for the component, and also it is used when resizing in real time. 141 | 142 | 定义父元素的初始宽度。 如果未指定,则自动计算。
143 | 使用此参数,您可以设置组件的边界区域,并在实时调整大小时使用它。 144 | ```html 145 | 146 | ``` 147 | 148 | #### parentH 149 | Type: `Number`
150 | Required: `false`
151 | Default: `0` 152 | 153 | Define the initial height of the parent element. If not specified it calculated automatically.
154 | With this parameter, you can set the bounding area for the component, and also it is used when resizing in real time. 155 | 156 | 定义父元素的初始高度。 如果未指定,则自动计算。 157 | 使用此参数,您可以设置组件的边界区域,并在实时调整大小时使用它。 158 | 159 | ```html 160 | 161 | ``` 162 | 163 | #### parentScaleX 164 | Type: `Number`
165 | Required: `false`
166 | Default: `1` 167 | 168 | Define the initial horizontal scale or the parent element. Same value in parent's transform: scale() css definition.
169 | The drag/resize and the sticks' sizes will computed with this value. 170 | 171 | 定义初始水平比例或父元素。父级的transform:scale()css定义中的值相同。
172 | 拖动/调整大小和杆的大小将使用该值计算。 173 | ```html 174 | 175 | ``` 176 | 177 | #### parentScaleY 178 | Type: `Number`
179 | Required: `false`
180 | Default: `1` 181 | 182 | Define the initial vertical scale or the parent element. Same value in parent's transform: scale() css definition.
183 | The drag/resize and the sticks' sizes will computed with this value. 184 | 185 | 定义初始垂直比例或父元素。父级的transform:scale()css定义中的值相同。
186 | 拖动/调整大小和杆的大小将使用该值计算。 187 | 188 | ```html 189 | 190 | ``` 191 | 192 | #### isDraggable 193 | Type: `Boolean`
194 | Required: `false`
195 | Default: `true` 196 | 197 | Determines whether the component should draggable. 198 | 199 | 确定组件是否应可拖动。 200 | 201 | 202 | ```html 203 | 204 | ``` 205 | 206 | #### isResizable 207 | Type: `Boolean`
208 | Required: `false`
209 | Default: `true` 210 | 211 | Determines whether the component should resize. 212 | 213 | 确定组件是否应调整大小。 214 | 215 | 216 | ```html 217 | 218 | ``` 219 | #### parentLimitation 220 | Type: `Boolean`
221 | Required: `false`
222 | Default: `false` 223 | 224 | Limits the scope of the component's change to its parent size. 225 | 226 | 将组件更改的范围限制为其父大小。 227 | 228 | 229 | ```html 230 | 231 | ``` 232 | 233 | #### snapToGrid 234 | Type: `Boolean`
235 | Required: `false`
236 | Default: `false` 237 | 238 | Determines whether the component should move and resize in predefined steps. 239 | 240 | ```html 241 | 242 | ``` 243 | 244 | #### gridX 245 | Type: `Number`
246 | Required: `false`
247 | Default: `50` 248 | 249 | Define the grid step size for the horizontal axis. Both sides of the component (left and right) will snap to this step. 250 | 251 | ```html 252 | 253 | ``` 254 | 255 | #### gridY 256 | Type: `Number`
257 | Required: `false`
258 | Default: `50` 259 | 260 | Define the grid step size for the vertical axis. Both sides of the component (top and bottom) will snap to this step. 261 | 262 | ```html 263 | 264 | ``` 265 | 266 | #### aspectRatio 267 | Type: `Boolean`
268 | Required: `false`
269 | Default: `false` 270 | 271 | Determines whether the component should retain its proportions. 272 | 273 | 确定组件是否应保持其比例。 274 | 275 | 276 | ```html 277 | 278 | ``` 279 | 280 | #### w 281 | Type: `Number|String`
282 | Required: `false`
283 | Default: `200` 284 | 285 | Define the initial width of the component.
286 | The value can either be a number >= 0 or the string 'auto'.
287 | If set to 'auto', the initial width value will be equal to the width of the content within the component. 288 | 289 | 定义组件的初始宽度。 290 | 291 | 292 | ```html 293 | 294 | ``` 295 | 296 | #### h 297 | Type: `Number|String`
298 | Required: `false`
299 | Default: `200` 300 | 301 | Define the initial height of the component.
302 | The value can either be a number >= 0 or the string 'auto'.
303 | If set to 'auto', the initial height value will be equal to the height of the content within the component. 304 | 305 | 定义组件的初始高度。 306 | 307 | 308 | 309 | ```html 310 | 311 | ``` 312 | 313 | #### minw 314 | Type: `Number`
315 | Required: `false`
316 | Default: `50` 317 | 318 | Define the minimal width of the component. 319 | 320 | 定义组件的初始宽度。 321 | 322 | 323 | 324 | ```html 325 | 326 | ``` 327 | 328 | #### minh 329 | Type: `Number`
330 | Required: `false`
331 | Default: `50` 332 | 333 | Define the minimal height of the component. 334 | 335 | 定义组件的最小高度。 336 | 337 | 338 | ```html 339 | 340 | ``` 341 | 342 | #### x 343 | Type: `Number`
344 | Required: `false`
345 | Default: `0` 346 | 347 | Define the initial x position of the component. 348 | 349 | 定义组件的初始X位置。 350 | 351 | 352 | ```html 353 | 354 | ``` 355 | 356 | #### y 357 | Type: `Number`
358 | Required: `false`
359 | Default: `0` 360 | 361 | Define the initial y position of the component. 362 | 363 | 定义组件的初始Y位置。 364 | 365 | 366 | ```html 367 | 368 | ``` 369 | 370 | #### z 371 | Type: `Number|String`
372 | Required: `false`
373 | Default: `auto` 374 | 375 | Define the zIndex of the component. 376 | 377 | 定义组件的zindex(层级)。 378 | 379 | ```html 380 | 381 | ``` 382 | 383 | #### stickSize 384 | Type: `Number`
385 | Required: `false`
386 | Default `8` 387 | 388 | Define the sticks' size. 389 | 390 | ```html 391 | 392 | ``` 393 | 394 | #### sticks 395 | Type: `Array`
396 | Required: `false`
397 | Default: `['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml']` 398 | 399 | Define the array of handles to restrict the element resizing: 400 | 401 | 定义句柄数组以限制元素大小调整: 402 | 403 | * `tl` - Top left 404 | * `tm` - Top middle 405 | * `tr` - Top right 406 | * `mr` - Middle right 407 | * `br` - Bottom right 408 | * `bm` - Bottom middle 409 | * `bl` - Bottom left 410 | * `ml` - Middle left 411 | 412 | ```html 413 | 414 | ``` 415 | 416 | #### axis 417 | Type: `String`
418 | Required: `false`
419 | Default: `both` 420 | 421 | Define the axis on which the element is draggable. Available values are `x`, `y`, `both` or `none`. 422 | 423 | 定义元素可拖动的轴。 可用值为`x`,`y`,`both`或`none`。 424 | 425 | ```html 426 | 427 | ``` 428 | 429 | #### dragHandle 430 | Type: `String`
431 | Required: `false` 432 | 433 | Defines the selector that should be used to drag the component. 434 | 435 | 定义应该用于拖动组件的选择器。 436 | 437 | ```html 438 | 439 | ``` 440 | 441 | #### dragCancel 442 | Type: `String`
443 | Required: `false` 444 | 445 | Defines a selector that should be used to prevent drag initialization. 446 | 447 | 定义应该用于防止拖动初始化的选择器。 448 | 449 | ```html 450 | 451 | ``` 452 | 453 | #### contentClass 454 | Type: `String`
455 | Required: `false` 456 | 457 | Defines a class that is applied on the div with the class vdr 458 | 459 | ```html 460 | 461 | ``` 462 | 463 | 464 | 465 | 466 | --- 467 | 468 | ### Events 469 | 470 | #### clicked 471 | 472 | Required: `false`
473 | Parameters: `Original event handler` 474 | 475 | Called whenever the component gets clicked. 476 | 477 | 单击组件时调用。 478 | 479 | ```html 480 | 481 | ``` 482 | 483 | #### activated 484 | 485 | Required: `false`
486 | Parameters: `-` 487 | 488 | Called whenever the component gets clicked, in order to show handles. 489 | 490 | 单击组件时调用,以显示句柄。 491 | 492 | ```html 493 | 494 | ``` 495 | 496 | #### deactivated 497 | 498 | Required: `false`
499 | Parameters: `-` 500 | 501 | Called whenever the user clicks anywhere outside the component, in order to deactivate it. 502 | 503 | 每当用户单击组件外部的任何位置时调用,以便将其停用。 504 | 505 | 506 | ```html 507 | 508 | ``` 509 | 510 | #### resizing 511 | 512 | Required: `false`
513 | Parameters: `object` 514 | 515 | ```javascript 516 | { 517 | left: Number, //the X position of the component 518 | top: Number, //the Y position of the component 519 | width: Number, //the width of the component 520 | height: Number //the height of the component 521 | } 522 | ``` 523 | 524 | Called whenever the component gets resized. 525 | 526 | 每当组件调整大小时调用。 527 | 528 | 529 | ```html 530 | 531 | ``` 532 | 533 | #### resizestop 534 | 535 | Required: `false`
536 | Parameters: `object` 537 | ```javascript 538 | { 539 | left: Number, //the X position of the component 540 | top: Number, //the Y position of the component 541 | width: Number, //the width of the component 542 | height: Number //the height of the component 543 | } 544 | ``` 545 | 546 | Called whenever the component stops getting resized. 547 | 548 | 每当组件停止调整大小时调用。 549 | 550 | 551 | ```html 552 | 553 | ``` 554 | 555 | #### dragging 556 | 557 | Required: `false`
558 | Parameters: `object` 559 | ```javascript 560 | { 561 | left: Number, //the X position of the component 562 | top: Number, //the Y position of the component 563 | width: Number, //the width of the component 564 | height: Number //the height of the component 565 | } 566 | ``` 567 | 568 | Called whenever the component gets dragged. 569 | 570 | 每当拖动组件时调用。 571 | 572 | 573 | ```html 574 | 575 | ``` 576 | 577 | #### dragstop 578 | 579 | Required: `false`
580 | Parameters: `object` 581 | ```javascript 582 | { 583 | left: Number, //the X position of the component 584 | top: Number, //the Y position of the component 585 | width: Number, //the width of the component 586 | height: Number //the height of the component 587 | } 588 | ``` 589 | 590 | 591 | Called whenever the component stops getting dragged. 592 | 593 | 每当组件停止拖动时调用。 594 | 595 | 596 | ```html 597 | 598 | ``` 599 | 600 | ## Contributing 601 | 602 | Any contribution to the code or any part of the documentation and any idea and/or suggestion are very welcome. 603 | 604 | ``` bash 605 | # serve with hot reload at localhost:8081 606 | npm run start 607 | 608 | # distribution build 609 | npm run build 610 | 611 | ``` 612 | 613 | ## License 614 | 615 | [MIT license](LICENSE) -------------------------------------------------------------------------------- /config/dev.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: { 3 | "app": './src/demo/app.js' 4 | }, 5 | build: { 6 | env: '"development"', 7 | sourceMap: true, 8 | uglify: false, 9 | cssSourceMap: false, 10 | gzip: false 11 | } 12 | }; -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var envConfig = process.env.NODE_ENV === 'develop' ? 3 | require('./dev.conf') : process.env.NODE_ENV === 'testing' ? 4 | require('./test.conf') : require('./prod.conf'); 5 | 6 | var deepExtend = require('deep-extend'); 7 | 8 | module.exports = deepExtend({ 9 | build: { 10 | distPath: path.resolve(__dirname, '../dist'), 11 | bundleAnalyzerReport: false 12 | }, 13 | server: { 14 | assetsPath: path.resolve(__dirname, '../static'), 15 | port: 8081, 16 | host: '0.0.0.0', 17 | autoOpenBrowser: true, 18 | } 19 | }, envConfig); -------------------------------------------------------------------------------- /config/prod.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: { 3 | "index": './src', 4 | "demo": './src/demo/app' 5 | }, 6 | build: { 7 | env: '"production"', 8 | sourceMap: false, 9 | uglify: true, 10 | cssSourceMap: false, 11 | gzip: false 12 | } 13 | }; -------------------------------------------------------------------------------- /config/test.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: { 3 | "app": './src/app.js' 4 | }, 5 | build: { 6 | env: '"testing"', 7 | sourceMap: true, 8 | uglify: true, 9 | cssSourceMap: false, 10 | gzip: false 11 | } 12 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-drag-resize", 3 | "version": "1.5.4", 4 | "description": "Vue Component for resize and drag elements", 5 | "author": "Kirill Murashov ", 6 | "main": "dist/index.js", 7 | "scripts": { 8 | "start": "NODE_ENV=develop webpack-dev-server -d", 9 | "svg": "vsvg -s ./src/demo/svg -t ./src/demo/icons", 10 | "build": "webpack" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/kirillmurashov/vue-drag-resize.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/kirillmurashov/vue-drag-resize/issues" 18 | }, 19 | "homepage": "https://github.com/kirillmurashov/vue-drag-resize.git", 20 | "devDependencies": { 21 | "acorn-dynamic-import": "^3.0.0", 22 | "autoprefixer": "^6.7.2", 23 | "@babel/core": "^7.12.1", 24 | "@babel/plugin-proposal-class-properties": "^7.12.1", 25 | "@babel/preset-env": "^7.12.1", 26 | "babel-loader": "^8.1.0", 27 | "browserify": "^14.4.0", 28 | "chai": "^3.5.0", 29 | "chalk": "^1.1.3", 30 | "compression-webpack-plugin": "^1.0.0", 31 | "connect-history-api-fallback": "^1.3.0", 32 | "copy-webpack-plugin": "^4.0.1", 33 | "cross-env": "^5.0.5", 34 | "css-color-function": "^1.3.0", 35 | "css-loader": "^0.28.7", 36 | "cssnano": "^3.10.0", 37 | "deep-extend": "^0.5.0", 38 | "es6-promise": "^4.1.1", 39 | "eslint": "^4.18.2", 40 | "eslint-config-airbnb-base": "^11.1.3", 41 | "eslint-friendly-formatter": "^2.0.7", 42 | "eslint-import-resolver-webpack": "^0.8.1", 43 | "eslint-loader": "^1.7.1", 44 | "eslint-plugin-html": "^2.0.0", 45 | "eslint-plugin-import": "^2.2.0", 46 | "eventsource-polyfill": "^0.9.6", 47 | "express": "^4.14.1", 48 | "extract-text-webpack-plugin": "^3.0.2", 49 | "file-loader": "^1.1.11", 50 | "file-type": "^6.1.0", 51 | "friendly-errors-webpack-plugin": "^1.1.3", 52 | "function-bind": "^1.1.0", 53 | "gulp": "^3.9.1", 54 | "gulp-browserify": "^0.5.1", 55 | "gulp-bump": "^2.4.0", 56 | "gulp-clean": "^0.3.2", 57 | "gulp-concat": "^2.6.1", 58 | "gulp-concat-util": "^0.5.5", 59 | "gulp-copy": "0.0.2", 60 | "gulp-delete-file": "^1.0.2", 61 | "gulp-dotify": "^0.1.2", 62 | "gulp-file": "^0.3.0", 63 | "gulp-header": "^1.8.9", 64 | "gulp-htmlmin": "^3.0.0", 65 | "gulp-iconfont": "^9.0.1", 66 | "gulp-iconfont-css": "^2.1.0", 67 | "gulp-if": "^2.0.1", 68 | "gulp-json-css": "^0.2.3", 69 | "gulp-json-editor": "^2.2.1", 70 | "gulp-jst2": "^0.1.4", 71 | "gulp-load-plugins": "^1.3.0", 72 | "gulp-logwarn": "0.0.9", 73 | "gulp-match": "^1.0.3", 74 | "gulp-merge": "^0.1.1", 75 | "gulp-mocha": "^3.0.1", 76 | "gulp-multi-dest": "0.0.4", 77 | "gulp-postcss": "^7.0.0", 78 | "gulp-rename": "^1.2.2", 79 | "gulp-replace": "^0.5.4", 80 | "gulp-rtlcss": "^1.0.0", 81 | "gulp-run-sequence": "^0.3.2", 82 | "gulp-sourcemaps": "^2.6.1", 83 | "gulp-uglify": "^2.0.0", 84 | "html-webpack-plugin": "^2.28.0", 85 | "http-proxy-middleware": "^0.17.3", 86 | "jade": "^1.11.0", 87 | "json-loader": "^0.5.4", 88 | "karma": "^1.4.1", 89 | "karma-chai": "^0.1.0", 90 | "karma-chai-dom": "^1.1.0", 91 | "karma-chrome-launcher": "^2.2.0", 92 | "karma-coverage": "^1.1.1", 93 | "karma-firefox-launcher": "^1.1.0", 94 | "karma-mocha": "^1.3.0", 95 | "karma-phantomjs-launcher": "^1.0.2", 96 | "karma-phantomjs-shim": "^1.4.0", 97 | "karma-safari-launcher": "^1.0.0", 98 | "karma-sinon-chai": "^1.3.1", 99 | "karma-sourcemap-loader": "^0.3.7", 100 | "karma-spec-reporter": "0.0.31", 101 | "karma-webpack": "^2.0.2", 102 | "less": "^2.7.2", 103 | "less-loader": "^4.0.3", 104 | "mini-css-extract-plugin": "^0.4.0", 105 | "minimist": "^1.2.0", 106 | "mocha": "^3.0.2", 107 | "node-gyp": "3.4.0", 108 | "opn": "^4.0.2", 109 | "optimize-css-assets-webpack-plugin": "^1.3.1", 110 | "ora": "^1.2.0", 111 | "path": "^0.12.7", 112 | "phantomjs-prebuilt": "^2.1.14", 113 | "postcss-color-function": "^4.0.0", 114 | "postcss-cssnext": "^3.0.2", 115 | "postcss-import": "^10.0.0", 116 | "postcss-loader": "^2.0.6", 117 | "require-dir": "^0.3.0", 118 | "rimraf": "^2.6.0", 119 | "run-sequence": "^2.1.0", 120 | "script-loader": "^0.7.0", 121 | "semver": "^5.3.0", 122 | "sinon": "^2.1.0", 123 | "sinon-chai": "^2.8.0", 124 | "streamqueue": "^1.1.1", 125 | "style-loader": "^0.18.2", 126 | "stylus": "^0.54.5", 127 | "stylus-loader": "^3.0.1", 128 | "sugarss": "^1.0.0", 129 | "uglifyjs-webpack-plugin": "^0.4.6", 130 | "underscore": "^1.8.3", 131 | "url": "^0.11.0", 132 | "url-loader": "^0.5.9", 133 | "vue": "^2.5.16", 134 | "vue-loader": "^15.0.3", 135 | "vue-style-loader": "^3.0.0", 136 | "vue-svgicon": "^2.1.3", 137 | "vue-template-compiler": "^2.5.16", 138 | "vuex": "^3.0.1", 139 | "watchjs": "0.0.0", 140 | "watchpack": "^1.5.0", 141 | "webpack": "^4.6.0", 142 | "webpack-bundle-analyzer": "^3.3.2", 143 | "webpack-cli": "^2.0.14", 144 | "webpack-dev-middleware": "^3.1.2", 145 | "webpack-dev-server": "^3.1.3", 146 | "webpack-hot-middleware": "^2.18.0", 147 | "webpack-merge": "^4.1.0", 148 | "yargs": "^5.0.0" 149 | }, 150 | "engines": { 151 | "node": ">= 4.0.0", 152 | "npm": ">= 3.0.0" 153 | }, 154 | "browserslist": [ 155 | "> 1%", 156 | "last 2 versions", 157 | "not ie <= 9" 158 | ], 159 | "license": "MIT", 160 | "dependencies": { 161 | "vue-drag-resize": "^1.5.0-rc3" 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-import': {}, 4 | 'postcss-cssnext': {}, 5 | 'cssnano': {autoprefixer: false} 6 | } 7 | }; -------------------------------------------------------------------------------- /src/components/vue-drag-resize.css: -------------------------------------------------------------------------------- 1 | .vdr { 2 | position: absolute; 3 | box-sizing: border-box; 4 | } 5 | .vdr.active:before{ 6 | content: ''; 7 | width: 100%; 8 | height: 100%; 9 | position: absolute; 10 | top: 0; 11 | left: 0; 12 | box-sizing: border-box; 13 | outline: 1px dashed #d6d6d6; 14 | } 15 | .vdr-stick { 16 | box-sizing: border-box; 17 | position: absolute; 18 | font-size: 1px; 19 | background: #ffffff; 20 | border: 1px solid #6c6c6c; 21 | box-shadow: 0 0 2px #bbb; 22 | } 23 | .inactive .vdr-stick { 24 | display: none; 25 | } 26 | .vdr-stick-tl, .vdr-stick-br { 27 | cursor: nwse-resize; 28 | } 29 | .vdr-stick-tm, .vdr-stick-bm { 30 | left: 50%; 31 | cursor: ns-resize; 32 | } 33 | .vdr-stick-tr, .vdr-stick-bl { 34 | cursor: nesw-resize; 35 | } 36 | .vdr-stick-ml, .vdr-stick-mr { 37 | top: 50%; 38 | cursor: ew-resize; 39 | } 40 | .vdr-stick.not-resizable{ 41 | display: none; 42 | } 43 | .content-container{ 44 | display: block; 45 | position: relative; 46 | } -------------------------------------------------------------------------------- /src/components/vue-drag-resize.html: -------------------------------------------------------------------------------- 1 |
6 |
7 | 8 |
9 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /src/components/vue-drag-resize.js: -------------------------------------------------------------------------------- 1 | const styleMapping = { 2 | y: { 3 | t: 'top', 4 | m: 'marginTop', 5 | b: 'bottom', 6 | }, 7 | x: { 8 | l: 'left', 9 | m: 'marginLeft', 10 | r: 'right', 11 | }, 12 | }; 13 | 14 | function addEvents(events) { 15 | events.forEach((cb, eventName) => { 16 | document.documentElement.addEventListener(eventName, cb); 17 | }); 18 | } 19 | 20 | function removeEvents(events) { 21 | events.forEach((cb, eventName) => { 22 | document.documentElement.removeEventListener(eventName, cb); 23 | }); 24 | } 25 | 26 | export default { 27 | name: 'vue-drag-resize', 28 | 29 | emits: ['clicked', 'dragging', 'dragstop', 'resizing', 'resizestop', 'activated', 'deactivated'], 30 | 31 | props: { 32 | stickSize: { 33 | type: Number, default: 8, 34 | }, 35 | parentScaleX: { 36 | type: Number, default: 1, 37 | }, 38 | parentScaleY: { 39 | type: Number, default: 1, 40 | }, 41 | isActive: { 42 | type: Boolean, default: false, 43 | }, 44 | preventActiveBehavior: { 45 | type: Boolean, default: false, 46 | }, 47 | isDraggable: { 48 | type: Boolean, default: true, 49 | }, 50 | isResizable: { 51 | type: Boolean, default: true, 52 | }, 53 | aspectRatio: { 54 | type: Boolean, default: false, 55 | }, 56 | parentLimitation: { 57 | type: Boolean, default: false, 58 | }, 59 | snapToGrid: { 60 | type: Boolean, default: false, 61 | }, 62 | gridX: { 63 | type: Number, 64 | default: 50, 65 | validator(val) { 66 | return val >= 0; 67 | }, 68 | }, 69 | gridY: { 70 | type: Number, 71 | default: 50, 72 | validator(val) { 73 | return val >= 0; 74 | }, 75 | }, 76 | parentW: { 77 | type: Number, 78 | default: 0, 79 | validator(val) { 80 | return val >= 0; 81 | }, 82 | }, 83 | parentH: { 84 | type: Number, 85 | default: 0, 86 | validator(val) { 87 | return val >= 0; 88 | }, 89 | }, 90 | w: { 91 | type: [String, Number], 92 | default: 200, 93 | validator(val) { 94 | return (typeof val === 'string') ? val === 'auto' : val >= 0; 95 | }, 96 | }, 97 | h: { 98 | type: [String, Number], 99 | default: 200, 100 | validator(val) { 101 | return (typeof val === 'string') ? val === 'auto' : val >= 0; 102 | }, 103 | }, 104 | minw: { 105 | type: Number, 106 | default: 50, 107 | validator(val) { 108 | return val >= 0; 109 | }, 110 | }, 111 | minh: { 112 | type: Number, 113 | default: 50, 114 | validator(val) { 115 | return val >= 0; 116 | }, 117 | }, 118 | x: { 119 | type: Number, 120 | default: 0, 121 | validator(val) { 122 | return typeof val === 'number'; 123 | }, 124 | }, 125 | y: { 126 | type: Number, 127 | default: 0, 128 | validator(val) { 129 | return typeof val === 'number'; 130 | }, 131 | }, 132 | z: { 133 | type: [String, Number], 134 | default: 'auto', 135 | validator(val) { 136 | return (typeof val === 'string') ? val === 'auto' : val >= 0; 137 | }, 138 | }, 139 | dragHandle: { 140 | type: String, 141 | default: null, 142 | }, 143 | dragCancel: { 144 | type: String, 145 | default: null, 146 | }, 147 | sticks: { 148 | type: Array, 149 | default() { 150 | return ['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml']; 151 | }, 152 | }, 153 | axis: { 154 | type: String, 155 | default: 'both', 156 | validator(val) { 157 | return ['x', 'y', 'both', 'none'].indexOf(val) !== -1; 158 | }, 159 | }, 160 | contentClass: { 161 | type: String, 162 | required: false, 163 | default: '', 164 | }, 165 | }, 166 | 167 | data() { 168 | return { 169 | fixAspectRatio: null, 170 | active: null, 171 | zIndex: null, 172 | parentWidth: null, 173 | parentHeight: null, 174 | left: null, 175 | top: null, 176 | right: null, 177 | bottom: null, 178 | minHeight: null, 179 | }; 180 | }, 181 | 182 | beforeCreate() { 183 | this.stickDrag = false; 184 | this.bodyDrag = false; 185 | this.dimensionsBeforeMove = { pointerX: 0, pointerY: 0, x: 0, y: 0, w: 0, h: 0 }; 186 | this.limits = { 187 | left: { min: null, max: null }, 188 | right: { min: null, max: null }, 189 | top: { min: null, max: null }, 190 | bottom: { min: null, max: null }, 191 | }; 192 | 193 | this.currentStick = null; 194 | }, 195 | 196 | mounted() { 197 | this.parentElement = this.$el.parentNode; 198 | this.parentWidth = this.parentW ? this.parentW : this.parentElement.clientWidth; 199 | this.parentHeight = this.parentH ? this.parentH : this.parentElement.clientHeight; 200 | 201 | this.left = this.x; 202 | this.top = this.y; 203 | this.right = this.parentWidth - (this.w === 'auto' ? this.$refs.container.scrollWidth : this.w) - this.left; 204 | this.bottom = this.parentHeight - (this.h === 'auto' ? this.$refs.container.scrollHeight : this.h) - this.top; 205 | 206 | this.domEvents = new Map([ 207 | ['mousemove', this.move], 208 | ['mouseup', this.up], 209 | ['mouseleave', this.up], 210 | ['mousedown', this.deselect], 211 | ['touchmove', this.move], 212 | ['touchend', this.up], 213 | ['touchcancel', this.up], 214 | ['touchstart', this.up], 215 | ]); 216 | 217 | addEvents(this.domEvents); 218 | 219 | if (this.dragHandle) { 220 | [...this.$el.querySelectorAll(this.dragHandle)].forEach((dragHandle) => { 221 | dragHandle.setAttribute('data-drag-handle', this._uid); 222 | }); 223 | } 224 | 225 | if (this.dragCancel) { 226 | [...this.$el.querySelectorAll(this.dragCancel)].forEach((cancelHandle) => { 227 | cancelHandle.setAttribute('data-drag-cancel', this._uid); 228 | }); 229 | } 230 | }, 231 | 232 | beforeDestroy() { 233 | removeEvents(this.domEvents); 234 | }, 235 | 236 | methods: { 237 | deselect() { 238 | if (this.preventActiveBehavior) { 239 | return; 240 | } 241 | this.active = false; 242 | }, 243 | 244 | move(ev) { 245 | if (!this.stickDrag && !this.bodyDrag) { 246 | return; 247 | } 248 | 249 | ev.stopPropagation(); 250 | 251 | const pageX = typeof ev.pageX !== 'undefined' ? ev.pageX : ev.touches[0].pageX; 252 | const pageY = typeof ev.pageY !== 'undefined' ? ev.pageY : ev.touches[0].pageY; 253 | 254 | const { dimensionsBeforeMove } = this; 255 | 256 | const delta = { 257 | x: (dimensionsBeforeMove.pointerX - pageX) / this.parentScaleX, 258 | y: (dimensionsBeforeMove.pointerY - pageY) / this.parentScaleY, 259 | }; 260 | 261 | if (this.stickDrag) { 262 | this.stickMove(delta); 263 | } 264 | 265 | if (this.bodyDrag) { 266 | if (this.axis === 'x') { 267 | delta.y = 0; 268 | } else if (this.axis === 'y') { 269 | delta.x = 0; 270 | } else if (this.axis === 'none') { 271 | return; 272 | } 273 | this.bodyMove(delta); 274 | } 275 | }, 276 | 277 | up(ev) { 278 | if (this.stickDrag) { 279 | this.stickUp(ev); 280 | } else if (this.bodyDrag) { 281 | this.bodyUp(ev); 282 | } 283 | }, 284 | 285 | bodyDown(ev) { 286 | const { target, button } = ev; 287 | 288 | if (!this.preventActiveBehavior) { 289 | this.active = true; 290 | } 291 | 292 | if (button && button !== 0) { 293 | return; 294 | } 295 | 296 | this.$emit('clicked', ev); 297 | 298 | if (!this.active) { 299 | return; 300 | } 301 | 302 | if (this.dragHandle && target.getAttribute('data-drag-handle') !== this._uid.toString()) { 303 | return; 304 | } 305 | 306 | if (this.dragCancel && target.getAttribute('data-drag-cancel') === this._uid.toString()) { 307 | return; 308 | } 309 | 310 | if (typeof ev.stopPropagation !== 'undefined') { 311 | ev.stopPropagation(); 312 | } 313 | 314 | if (typeof ev.preventDefault !== 'undefined') { 315 | ev.preventDefault(); 316 | } 317 | 318 | if (this.isDraggable) { 319 | this.bodyDrag = true; 320 | } 321 | 322 | const pointerX = typeof ev.pageX !== 'undefined' ? ev.pageX : ev.touches[0].pageX; 323 | const pointerY = typeof ev.pageY !== 'undefined' ? ev.pageY : ev.touches[0].pageY; 324 | 325 | this.saveDimensionsBeforeMove({ pointerX, pointerY }); 326 | 327 | if (this.parentLimitation) { 328 | this.limits = this.calcDragLimitation(); 329 | } 330 | }, 331 | 332 | bodyMove(delta) { 333 | const { dimensionsBeforeMove, parentWidth, parentHeight, gridX, gridY, width, height } = this; 334 | 335 | let newTop = dimensionsBeforeMove.top - delta.y; 336 | let newBottom = dimensionsBeforeMove.bottom + delta.y; 337 | let newLeft = dimensionsBeforeMove.left - delta.x; 338 | let newRight = dimensionsBeforeMove.right + delta.x; 339 | 340 | if (this.snapToGrid) { 341 | let alignTop = true; 342 | let alignLeft = true; 343 | 344 | let diffT = newTop - Math.floor(newTop / gridY) * gridY; 345 | let diffB = (parentHeight - newBottom) - Math.floor((parentHeight - newBottom) / gridY) * gridY; 346 | let diffL = newLeft - Math.floor(newLeft / gridX) * gridX; 347 | let diffR = (parentWidth - newRight) - Math.floor((parentWidth - newRight) / gridX) * gridX; 348 | 349 | if (diffT > (gridY / 2)) { 350 | diffT -= gridY; 351 | } 352 | if (diffB > (gridY / 2)) { 353 | diffB -= gridY; 354 | } 355 | if (diffL > (gridX / 2)) { 356 | diffL -= gridX; 357 | } 358 | if (diffR > (gridX / 2)) { 359 | diffR -= gridX; 360 | } 361 | 362 | if (Math.abs(diffB) < Math.abs(diffT)) { 363 | alignTop = false; 364 | } 365 | if (Math.abs(diffR) < Math.abs(diffL)) { 366 | alignLeft = false; 367 | } 368 | 369 | newTop -= (alignTop ? diffT : diffB); 370 | newBottom = parentHeight - height - newTop; 371 | newLeft -= (alignLeft ? diffL : diffR); 372 | newRight = parentWidth - width - newLeft; 373 | } 374 | 375 | ({ 376 | newLeft: this.left, 377 | newRight: this.right, 378 | newTop: this.top, 379 | newBottom: this.bottom, 380 | } = this.rectCorrectionByLimit({ newLeft, newRight, newTop, newBottom })); 381 | 382 | this.$emit('dragging', this.rect); 383 | }, 384 | 385 | bodyUp() { 386 | this.bodyDrag = false; 387 | this.$emit('dragging', this.rect); 388 | this.$emit('dragstop', this.rect); 389 | 390 | this.dimensionsBeforeMove = { pointerX: 0, pointerY: 0, x: 0, y: 0, w: 0, h: 0 }; 391 | 392 | this.limits = { 393 | left: { min: null, max: null }, 394 | right: { min: null, max: null }, 395 | top: { min: null, max: null }, 396 | bottom: { min: null, max: null }, 397 | }; 398 | }, 399 | 400 | stickDown(stick, ev, force = false) { 401 | if ((!this.isResizable || !this.active) && !force) { 402 | return; 403 | } 404 | 405 | this.stickDrag = true; 406 | 407 | const pointerX = typeof ev.pageX !== 'undefined' ? ev.pageX : ev.touches[0].pageX; 408 | const pointerY = typeof ev.pageY !== 'undefined' ? ev.pageY : ev.touches[0].pageY; 409 | 410 | this.saveDimensionsBeforeMove({ pointerX, pointerY }); 411 | 412 | this.currentStick = stick; 413 | 414 | this.limits = this.calcResizeLimits(); 415 | }, 416 | 417 | saveDimensionsBeforeMove({ pointerX, pointerY }) { 418 | this.dimensionsBeforeMove.pointerX = pointerX; 419 | this.dimensionsBeforeMove.pointerY = pointerY; 420 | 421 | this.dimensionsBeforeMove.left = this.left; 422 | this.dimensionsBeforeMove.right = this.right; 423 | this.dimensionsBeforeMove.top = this.top; 424 | this.dimensionsBeforeMove.bottom = this.bottom; 425 | 426 | this.dimensionsBeforeMove.width = this.width; 427 | this.dimensionsBeforeMove.height = this.height; 428 | 429 | this.aspectFactor = this.width / this.height; 430 | }, 431 | 432 | stickMove(delta) { 433 | const { 434 | currentStick, 435 | dimensionsBeforeMove, 436 | gridY, 437 | gridX, 438 | snapToGrid, 439 | parentHeight, 440 | parentWidth, 441 | } = this; 442 | 443 | let newTop = dimensionsBeforeMove.top; 444 | let newBottom = dimensionsBeforeMove.bottom; 445 | let newLeft = dimensionsBeforeMove.left; 446 | let newRight = dimensionsBeforeMove.right; 447 | switch(currentStick[0]) { 448 | case 'b': 449 | newBottom = dimensionsBeforeMove.bottom + delta.y; 450 | 451 | if (snapToGrid) { 452 | newBottom = parentHeight - Math.round((parentHeight - newBottom) / gridY) * gridY; 453 | } 454 | 455 | break; 456 | 457 | case 't': 458 | newTop = dimensionsBeforeMove.top - delta.y; 459 | 460 | if (snapToGrid) { 461 | newTop = Math.round(newTop / gridY) * gridY; 462 | } 463 | 464 | break; 465 | default: 466 | break; 467 | } 468 | 469 | switch(currentStick[1]) { 470 | case 'r': 471 | newRight = dimensionsBeforeMove.right + delta.x; 472 | 473 | if (snapToGrid) { 474 | newRight = parentWidth - Math.round((parentWidth - newRight) / gridX) * gridX; 475 | } 476 | 477 | break; 478 | 479 | case 'l': 480 | newLeft = dimensionsBeforeMove.left - delta.x; 481 | 482 | if (snapToGrid) { 483 | newLeft = Math.round(newLeft / gridX) * gridX; 484 | } 485 | 486 | break; 487 | default: 488 | break; 489 | } 490 | 491 | ({ 492 | newLeft, 493 | newRight, 494 | newTop, 495 | newBottom, 496 | } = this.rectCorrectionByLimit({ newLeft, newRight, newTop, newBottom })); 497 | 498 | if (this.aspectRatio) { 499 | ({ 500 | newLeft, 501 | newRight, 502 | newTop, 503 | newBottom, 504 | } = this.rectCorrectionByAspectRatio({ newLeft, newRight, newTop, newBottom })); 505 | } 506 | 507 | this.left = newLeft; 508 | this.right = newRight; 509 | this.top = newTop; 510 | this.bottom = newBottom; 511 | 512 | this.$emit('resizing', this.rect); 513 | }, 514 | 515 | stickUp() { 516 | this.stickDrag = false; 517 | this.dimensionsBeforeMove = { 518 | pointerX: 0, 519 | pointerY: 0, 520 | x: 0, 521 | y: 0, 522 | w: 0, 523 | h: 0, 524 | }; 525 | this.limits = { 526 | left: { min: null, max: null }, 527 | right: { min: null, max: null }, 528 | top: { min: null, max: null }, 529 | bottom: { min: null, max: null }, 530 | }; 531 | 532 | this.$emit('resizing', this.rect); 533 | this.$emit('resizestop', this.rect); 534 | }, 535 | 536 | calcDragLimitation() { 537 | const { parentWidth, parentHeight } = this; 538 | 539 | return { 540 | left: { min: 0, max: parentWidth - this.width }, 541 | right: { min: 0, max: parentWidth - this.width }, 542 | top: { min: 0, max: parentHeight - this.height }, 543 | bottom: { min: 0, max: parentHeight - this.height }, 544 | }; 545 | }, 546 | 547 | calcResizeLimits() { 548 | const { aspectFactor, width, height, bottom, top, left, right } = this; 549 | let { minh: minHeight, minw: minWidth } = this; 550 | 551 | const parentLim = this.parentLimitation ? 0 : null; 552 | 553 | if (this.aspectRatio) { 554 | if (minWidth / minHeight > aspectFactor) { 555 | minHeight = minWidth / aspectFactor; 556 | } else { 557 | minWidth = aspectFactor * minHeight; 558 | } 559 | } 560 | 561 | const limits = { 562 | left: { min: parentLim, max: left + (width - minWidth) }, 563 | right: { min: parentLim, max: right + (width - minWidth) }, 564 | top: { min: parentLim, max: top + (height - minHeight) }, 565 | bottom: { min: parentLim, max: bottom + (height - minHeight) }, 566 | }; 567 | 568 | if (this.aspectRatio) { 569 | const aspectLimits = { 570 | left: { 571 | min: left - (Math.min(top, bottom) * aspectFactor) * 2, 572 | max: left + ((((height - minHeight) / 2) * aspectFactor) * 2), 573 | }, 574 | right: { 575 | min: right - (Math.min(top, bottom) * aspectFactor) * 2, 576 | max: right + ((((height - minHeight) / 2) * aspectFactor) * 2), 577 | }, 578 | top: { 579 | min: top - (Math.min(left, right) / aspectFactor) * 2, 580 | max: top + ((((width - minWidth) / 2) / aspectFactor) * 2), 581 | }, 582 | bottom: { 583 | min: bottom - (Math.min(left, right) / aspectFactor) * 2, 584 | max: bottom + ((((width - minWidth) / 2) / aspectFactor) * 2), 585 | }, 586 | }; 587 | 588 | if (this.currentStick[0] === 'm') { 589 | limits.left = { 590 | min: Math.max(limits.left.min, aspectLimits.left.min), 591 | max: Math.min(limits.left.max, aspectLimits.left.max), 592 | }; 593 | limits.right = { 594 | min: Math.max(limits.right.min, aspectLimits.right.min), 595 | max: Math.min(limits.right.max, aspectLimits.right.max), 596 | }; 597 | 598 | } else if (this.currentStick[1] === 'm') { 599 | limits.top = { 600 | min: Math.max(limits.top.min, aspectLimits.top.min), 601 | max: Math.min(limits.top.max, aspectLimits.top.max), 602 | }; 603 | limits.bottom = { 604 | min: Math.max(limits.bottom.min, aspectLimits.bottom.min), 605 | max: Math.min(limits.bottom.max, aspectLimits.bottom.max), 606 | }; 607 | } 608 | } 609 | 610 | return limits; 611 | }, 612 | 613 | sideCorrectionByLimit(limit, current) { 614 | let value = current; 615 | 616 | if (limit.min !== null && current < limit.min) { 617 | value = limit.min; 618 | } else if (limit.max !== null && limit.max < current) { 619 | value = limit.max; 620 | } 621 | 622 | return value; 623 | }, 624 | 625 | rectCorrectionByLimit(rect) { 626 | const { limits } = this; 627 | let { newRight, newLeft, newBottom, newTop } = rect; 628 | 629 | newLeft = this.sideCorrectionByLimit(limits.left, newLeft); 630 | newRight = this.sideCorrectionByLimit(limits.right, newRight); 631 | newTop = this.sideCorrectionByLimit(limits.top, newTop); 632 | newBottom = this.sideCorrectionByLimit(limits.bottom, newBottom); 633 | 634 | return { 635 | newLeft, 636 | newRight, 637 | newTop, 638 | newBottom, 639 | }; 640 | }, 641 | 642 | rectCorrectionByAspectRatio(rect) { 643 | let { newLeft, newRight, newTop, newBottom } = rect; 644 | const { parentWidth, parentHeight, currentStick, aspectFactor, dimensionsBeforeMove } = this; 645 | 646 | let newWidth = parentWidth - newLeft - newRight; 647 | let newHeight = parentHeight - newTop - newBottom; 648 | 649 | if (currentStick[1] === 'm') { 650 | const deltaHeight = newHeight - dimensionsBeforeMove.height; 651 | 652 | newLeft -= (deltaHeight * aspectFactor) / 2; 653 | newRight -= (deltaHeight * aspectFactor) / 2; 654 | } else if (currentStick[0] === 'm') { 655 | const deltaWidth = newWidth - dimensionsBeforeMove.width; 656 | 657 | newTop -= (deltaWidth / aspectFactor) / 2; 658 | newBottom -= (deltaWidth / aspectFactor) / 2; 659 | } else if (newWidth / newHeight > aspectFactor) { 660 | newWidth = aspectFactor * newHeight; 661 | 662 | if (currentStick[1] === 'l') { 663 | newLeft = parentWidth - newRight - newWidth; 664 | } else { 665 | newRight = parentWidth - newLeft - newWidth; 666 | } 667 | } else { 668 | newHeight = newWidth / aspectFactor; 669 | 670 | if (currentStick[0] === 't') { 671 | newTop = parentHeight - newBottom - newHeight; 672 | } else { 673 | newBottom = parentHeight - newTop - newHeight; 674 | } 675 | } 676 | 677 | return { newLeft, newRight, newTop, newBottom }; 678 | }, 679 | }, 680 | 681 | computed: { 682 | positionStyle() { 683 | return { 684 | top: this.top + 'px', 685 | left: this.left + 'px', 686 | zIndex: this.zIndex, 687 | }; 688 | }, 689 | 690 | sizeStyle(){ 691 | return { 692 | width: this.w == 'auto' ? 'auto' : this.width + 'px', 693 | height: this.h == 'auto' ? 'auto' : this.height + 'px' 694 | }; 695 | }, 696 | 697 | vdrStick() { 698 | return (stick) => { 699 | const stickStyle = { 700 | width: `${this.stickSize / this.parentScaleX}px`, 701 | height: `${this.stickSize / this.parentScaleY}px`, 702 | }; 703 | stickStyle[styleMapping.y[stick[0]]] = `${this.stickSize / this.parentScaleX / -2}px`; 704 | stickStyle[styleMapping.x[stick[1]]] = `${this.stickSize / this.parentScaleX / -2}px`; 705 | return stickStyle; 706 | }; 707 | }, 708 | 709 | width() { 710 | return this.parentWidth - this.left - this.right; 711 | }, 712 | 713 | height() { 714 | return this.parentHeight - this.top - this.bottom; 715 | }, 716 | 717 | rect() { 718 | return { 719 | left: Math.round(this.left), 720 | top: Math.round(this.top), 721 | width: Math.round(this.width), 722 | height: Math.round(this.height), 723 | }; 724 | }, 725 | }, 726 | 727 | watch: { 728 | active(isActive) { 729 | if (isActive) { 730 | this.$emit('activated'); 731 | } else { 732 | this.$emit('deactivated'); 733 | } 734 | }, 735 | 736 | isActive: { 737 | immediate: true, 738 | handler(val) { 739 | this.active = val; 740 | }, 741 | }, 742 | 743 | z: { 744 | immediate: true, 745 | handler(val) { 746 | if (val >= 0 || val === 'auto') { 747 | this.zIndex = val; 748 | } 749 | }, 750 | }, 751 | 752 | x: { 753 | handler(newVal, oldVal) { 754 | if (this.stickDrag || this.bodyDrag || (newVal === this.left)) { 755 | return; 756 | } 757 | 758 | const delta = oldVal - newVal; 759 | 760 | this.bodyDown({ pageX: this.left, pageY: this.top }); 761 | this.bodyMove({ x: delta, y: 0 }); 762 | 763 | this.$nextTick(() => { 764 | this.bodyUp(); 765 | }); 766 | }, 767 | }, 768 | 769 | y: { 770 | handler(newVal, oldVal) { 771 | if (this.stickDrag || this.bodyDrag || (newVal === this.top)) { 772 | return; 773 | } 774 | 775 | const delta = oldVal - newVal; 776 | 777 | this.bodyDown({ pageX: this.left, pageY: this.top }); 778 | this.bodyMove({ x: 0, y: delta }); 779 | 780 | this.$nextTick(() => { 781 | this.bodyUp(); 782 | }); 783 | }, 784 | }, 785 | 786 | w: { 787 | handler(newVal, oldVal) { 788 | if (this.stickDrag || this.bodyDrag || (newVal === this.width)) { 789 | return; 790 | } 791 | 792 | const stick = 'mr'; 793 | const delta = oldVal - newVal; 794 | 795 | this.stickDown(stick, { pageX: this.right, pageY: this.top + (this.height / 2) }, true); 796 | this.stickMove({ x: delta, y: 0 }); 797 | 798 | this.$nextTick(() => { 799 | this.stickUp(); 800 | }); 801 | }, 802 | }, 803 | 804 | h: { 805 | handler(newVal, oldVal) { 806 | if (this.stickDrag || this.bodyDrag || (newVal === this.height)) { 807 | return; 808 | } 809 | 810 | const stick = 'bm'; 811 | const delta = oldVal - newVal; 812 | 813 | this.stickDown(stick, { pageX: this.left + (this.width / 2), pageY: this.bottom }, true); 814 | this.stickMove({ x: 0, y: delta }); 815 | 816 | this.$nextTick(() => { 817 | this.stickUp(); 818 | }); 819 | }, 820 | }, 821 | 822 | parentW(val) { 823 | this.right = val - this.width - this.left; 824 | this.parentWidth = val; 825 | }, 826 | 827 | parentH(val) { 828 | this.bottom = val - this.height - this.top; 829 | this.parentHeight = val; 830 | }, 831 | }, 832 | }; 833 | -------------------------------------------------------------------------------- /src/components/vue-drag-resize.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/demo/app.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import app from './app.vue'; 3 | import store from './store'; 4 | import * as svgicon from 'vue-svgicon'; 5 | 6 | Vue.use(svgicon, { 7 | tagName: 'svgicon' 8 | }); 9 | 10 | new Vue({ 11 | el: '#app', 12 | store, 13 | template: '', 14 | components: { app } 15 | }); -------------------------------------------------------------------------------- /src/demo/app.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 73 | 74 | -------------------------------------------------------------------------------- /src/demo/components/toolbar/toolbar.css: -------------------------------------------------------------------------------- 1 | :root{ 2 | --toolbar-width: 220px; 3 | } 4 | .toolbar{ 5 | position: absolute; 6 | right: 0; 7 | top: 0; 8 | width: var(--toolbar-width); 9 | padding: 15px 15px 0 15px; 10 | box-shadow: 0 0 2px #AAA; 11 | box-sizing: border-box; 12 | background-color: white; 13 | margin: 30px 30px 30px 0; 14 | } 15 | .toolbar-wh-row{ 16 | 17 | margin-bottom: 20px; 18 | } 19 | .toolbar-row-title{ 20 | width: var(--toolbar-width); 21 | font-size: 14px; 22 | font-family: 'Lato', sans-serif; 23 | font-weight: 500; 24 | margin: 0 0 3px 0; 25 | color: #1A173B; 26 | } 27 | .toolbar-position-inp, .toolbar-size-inp{ 28 | width: 90px; 29 | font-size: 11px; 30 | color: #BBB; 31 | font-weight: 300; 32 | display: inline-block; 33 | position: relative; 34 | } 35 | 36 | .toolbar-size-inp input,.toolbar-position-inp input{ 37 | width: 70px; 38 | display: inline-block; 39 | border: 1px solid #bfbfca; 40 | margin-top: 2px; 41 | height: 16px; 42 | } 43 | 44 | .toolbar-size-inp input[disabled],.toolbar-position-inp input[disabled]{ 45 | border: 1px solid #dcdce7; 46 | color: #AAAAAA; 47 | } 48 | 49 | .position-lock-icon, .size-lock-icon{ 50 | position: absolute; 51 | bottom: 3px; 52 | right: 17px; 53 | cursor: pointer; 54 | } 55 | .size-lock-icon{ 56 | bottom: 2px; 57 | right: -3px; 58 | } 59 | 60 | .toolbar-check-inp{ 61 | color: #445477; 62 | font-size: 13px; 63 | width: 180px; 64 | display: inline-block; 65 | margin: 2px 0; 66 | } 67 | .toolbar-row-title+label{ 68 | margin-top: 5px; 69 | } 70 | .toolbar-check-inp input{ 71 | border: 1px solid #bfbfca; 72 | } 73 | .to-top-icon, .to-bottom-icon{ 74 | margin: 10px 30px; 75 | cursor: pointer; 76 | } 77 | -------------------------------------------------------------------------------- /src/demo/components/toolbar/toolbar.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Position

4 | top 5 | 6 | 7 | 14 | 15 | left 16 | 17 | 24 | 25 |
26 |
27 |

Size

28 | width 29 | 30 | 37 | 38 | height 39 | 40 | 41 |
42 | 43 |
44 |

Minimal size

45 | width 46 | 47 | 48 | height 49 | 50 | 51 |
52 | 53 |
54 |

Restrictions

55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
63 |
-------------------------------------------------------------------------------- /src/demo/components/toolbar/toolbar.js: -------------------------------------------------------------------------------- 1 | export default { 2 | computed: { 3 | activeRect() { 4 | return this.$store.getters['rect/getActive']; 5 | }, 6 | 7 | width() { 8 | return this.activeRect === null ? '' : this.$store.state.rect.rects[this.activeRect].width 9 | }, 10 | 11 | height() { 12 | return this.activeRect === null ? '' : this.$store.state.rect.rects[this.activeRect].height 13 | }, 14 | 15 | top() { 16 | return this.activeRect === null ? '' : this.$store.state.rect.rects[this.activeRect].top 17 | }, 18 | 19 | left() { 20 | return this.activeRect === null ? '' : this.$store.state.rect.rects[this.activeRect].left 21 | }, 22 | 23 | minw() { 24 | return this.activeRect === null ? '' : this.$store.state.rect.rects[this.activeRect].minw 25 | }, 26 | 27 | minh() { 28 | return this.activeRect === null ? '' : this.$store.state.rect.rects[this.activeRect].minh 29 | }, 30 | 31 | aspectRatio() { 32 | return this.activeRect === null ? false : this.$store.state.rect.rects[this.activeRect].aspectRatio; 33 | }, 34 | 35 | parentLim() { 36 | return this.activeRect === null ? false : this.$store.state.rect.rects[this.activeRect].parentLim; 37 | }, 38 | 39 | draggable() { 40 | return this.activeRect === null ? false : this.$store.state.rect.rects[this.activeRect].draggable; 41 | }, 42 | 43 | resizable() { 44 | return this.activeRect === null ? false : this.$store.state.rect.rects[this.activeRect].resizable; 45 | }, 46 | 47 | snapToGrid() { 48 | return this.activeRect === null ? false : this.$store.state.rect.rects[this.activeRect].snapToGrid; 49 | }, 50 | 51 | topIsLocked() { 52 | if (this.activeRect === null) { 53 | return false; 54 | } 55 | return (this.$store.state.rect.rects[this.activeRect].axis === 'x' || 56 | this.$store.state.rect.rects[this.activeRect].axis === 'none') 57 | }, 58 | 59 | leftIsLocked() { 60 | if (this.activeRect === null) { 61 | return false; 62 | } 63 | return (this.$store.state.rect.rects[this.activeRect].axis === 'y' || 64 | this.$store.state.rect.rects[this.activeRect].axis === 'none') 65 | }, 66 | 67 | zIndex() { 68 | if (this.activeRect === null) { 69 | return null; 70 | } 71 | 72 | return this.$store.state.rect.rects[this.activeRect].zIndex === 1 ? 'isFirst' : 73 | this.$store.state.rect.rects[this.activeRect].zIndex === this.$store.state.rect.rects.length ? 'isLast' : 'normal' 74 | 75 | } 76 | }, 77 | methods: { 78 | toggleYLock() { 79 | if (this.activeRect === null) { 80 | return 81 | } 82 | 83 | this.$store.dispatch('rect/changeYLock', {id: this.activeRect}); 84 | }, 85 | toggleXLock() { 86 | if (this.activeRect === null) { 87 | return 88 | } 89 | 90 | this.$store.dispatch('rect/changeXLock', {id: this.activeRect}); 91 | }, 92 | 93 | toggleAspect() { 94 | if (this.activeRect === null) { 95 | return 96 | } 97 | if (!this.$store.state.rect.rects[this.activeRect].aspectRatio) { 98 | this.$store.dispatch('rect/setAspect', {id: this.activeRect}); 99 | } else { 100 | this.$store.dispatch('rect/unsetAspect', {id: this.activeRect}); 101 | } 102 | }, 103 | 104 | toggleParentLimitation() { 105 | this.$store.dispatch('rect/toggleParentLimitation', {id: this.activeRect}); 106 | }, 107 | 108 | toggleResizable() { 109 | this.$store.dispatch('rect/toggleResizable', {id: this.activeRect}); 110 | }, 111 | 112 | toggleDraggable() { 113 | this.$store.dispatch('rect/toggleDraggable', {id: this.activeRect}); 114 | }, 115 | 116 | toggleSnapToGrid() { 117 | this.$store.dispatch('rect/toggleSnapToGrid', {id: this.activeRect}); 118 | }, 119 | 120 | toTop() { 121 | this.$store.dispatch('rect/changeZToTop', {id: this.activeRect}); 122 | }, 123 | 124 | toBottom() { 125 | this.$store.dispatch('rect/changeZToBottom', {id: this.activeRect}); 126 | }, 127 | 128 | changeMinWidth(ev) { 129 | let minw = parseInt(ev.target.value); 130 | if (typeof minw !== 'number' || isNaN(minw)) { 131 | minw = 1; 132 | } 133 | 134 | if (minw <= 0) { 135 | minw = 1; 136 | } else if (minw > this.$store.state.rect.rects[this.activeRect].width) { 137 | minw = this.$store.state.rect.rects[this.activeRect].width; 138 | } 139 | 140 | ev.target.value = minw; 141 | 142 | this.$store.dispatch('rect/setMinWidth', {id: this.activeRect, width: minw}); 143 | }, 144 | 145 | changeMinHeight(ev) { 146 | let minh = parseInt(ev.target.value); 147 | 148 | if (typeof minh !== 'number' || isNaN(minh)) { 149 | minh = 1; 150 | } 151 | 152 | if (minh <= 0) { 153 | minh = 1; 154 | } else if (minh > this.$store.state.rect.rects[this.activeRect].height) { 155 | minh = this.$store.state.rect.rects[this.activeRect].height; 156 | } 157 | 158 | ev.target.value = minh; 159 | 160 | this.$store.dispatch('rect/setMinHeight', {id: this.activeRect, height: minh}); 161 | }, 162 | 163 | changeTop(ev) { 164 | let top = parseInt(ev.target.value); 165 | 166 | if (typeof top !== 'number' || isNaN(top)) { 167 | top = this.$store.state.rect.rects[this.activeRect].top; 168 | ev.target.value = top; 169 | return 170 | } 171 | 172 | this.$store.dispatch('rect/setTop', {id: this.activeRect, top: top}); 173 | }, 174 | 175 | changeLeft(ev) { 176 | let left = parseInt(ev.target.value); 177 | 178 | if (typeof left !== 'number' || isNaN(left)) { 179 | left = this.$store.state.rect.rects[this.activeRect].left; 180 | ev.target.value = left; 181 | } 182 | 183 | this.$store.dispatch('rect/setLeft', {id: this.activeRect, left: left}); 184 | }, 185 | 186 | changeWidth(ev){ 187 | let width = parseInt(ev.target.value); 188 | 189 | if (typeof width !== 'number' || isNaN(width)) { 190 | width = this.$store.state.rect.rects[this.activeRect].width; 191 | ev.target.value = width; 192 | } 193 | 194 | this.$store.dispatch('rect/setWidth', {id: this.activeRect, width: width}); 195 | }, 196 | 197 | changeHeight(ev){ 198 | let height = parseInt(ev.target.value); 199 | 200 | if (typeof height !== 'number' || isNaN(height)) { 201 | height = this.$store.state.rect.rects[this.activeRect].height; 202 | ev.target.value = height; 203 | } 204 | 205 | this.$store.dispatch('rect/setHeight', {id: this.activeRect, height: height}); 206 | } 207 | } 208 | } -------------------------------------------------------------------------------- /src/demo/components/toolbar/toolbar.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/demo/icons/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('./lock') 3 | require('./toBottom') 4 | require('./toTop') 5 | -------------------------------------------------------------------------------- /src/demo/icons/lock.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var icon = require('vue-svgicon') 3 | icon.register({ 4 | 'lock': { 5 | width: 100, 6 | height: 100, 7 | viewBox: '0 0 100 100', 8 | data: '' 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /src/demo/icons/toBottom.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var icon = require('vue-svgicon') 3 | icon.register({ 4 | 'toBottom': { 5 | width: 100, 6 | height: 100, 7 | viewBox: '0 0 100 100', 8 | data: '' 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /src/demo/icons/toTop.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var icon = require('vue-svgicon') 3 | icon.register({ 4 | 'toTop': { 5 | width: 100, 6 | height: 100, 7 | viewBox: '0 0 100 100', 8 | data: '' 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /src/demo/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import rect from './modules/rect' 4 | 5 | Vue.use(Vuex); 6 | 7 | const debug = process.env.NODE_ENV !== 'production'; 8 | 9 | export default new Vuex.Store({ 10 | /** 11 | * Assign the modules to the store 12 | */ 13 | modules: {'rect': rect }, 14 | 15 | /** 16 | * If strict mode should be enabled 17 | */ 18 | strict: debug 19 | }); 20 | -------------------------------------------------------------------------------- /src/demo/store/modules/rect/actions.js: -------------------------------------------------------------------------------- 1 | import types, {CHANGE_ZINDEX} from './mutation-types'; 2 | 3 | export default { 4 | setActive({commit, state}, {id}) { 5 | for (let i = 0, l = state.rects.length; i < l; i++) { 6 | if (i === id) { 7 | commit(types.ENABLE_ACTIVE, i); 8 | continue; 9 | } 10 | 11 | commit(types.DISABLE_ACTIVE, i); 12 | } 13 | }, 14 | unsetActive({commit}, {id}) { 15 | commit(types.DISABLE_ACTIVE, id); 16 | }, 17 | 18 | toggleDraggable({commit, state}, {id}) { 19 | if (!state.rects[id].draggable) { 20 | commit(types.ENABLE_DRAGGABLE, id); 21 | } else { 22 | commit(types.DISABLE_DRAGGABLE, id); 23 | } 24 | }, 25 | 26 | toggleResizable({commit, state}, {id}) { 27 | if (!state.rects[id].resizable) { 28 | commit(types.ENABLE_RESIZABLE, id); 29 | } else { 30 | commit(types.DISABLE_RESIZABLE, id); 31 | } 32 | }, 33 | 34 | toggleParentLimitation({commit, state}, {id}) { 35 | if (!state.rects[id].parentLim) { 36 | commit(types.ENABLE_PARENT_LIMITATION, id); 37 | } else { 38 | commit(types.DISABLE_PARENT_LIMITATION, id); 39 | } 40 | }, 41 | 42 | toggleSnapToGrid({commit, state}, {id}) { 43 | if (!state.rects[id].snapToGrid) { 44 | commit(types.ENABLE_SNAP_TO_GRID, id); 45 | } else { 46 | commit(types.DISABLE_SNAP_TO_GRID, id); 47 | } 48 | }, 49 | 50 | setAspect({commit}, {id}) { 51 | commit(types.ENABLE_ASPECT, id); 52 | }, 53 | unsetAspect({commit}, {id}) { 54 | commit(types.DISABLE_ASPECT, id); 55 | }, 56 | 57 | setWidth({commit}, {id, width}) { 58 | commit(types.CHANGE_WIDTH, {id, width}); 59 | }, 60 | 61 | setHeight({commit}, {id, height}) { 62 | commit(types.CHANGE_HEIGHT, {id, height}); 63 | }, 64 | 65 | setTop({commit}, {id, top}) { 66 | commit(types.CHANGE_TOP, {id, top}); 67 | }, 68 | 69 | setLeft({commit}, {id, left}) { 70 | commit(types.CHANGE_LEFT, {id, left}); 71 | }, 72 | 73 | changeXLock({commit, state}, {id}) { 74 | switch (state.rects[id].axis) { 75 | case 'both': 76 | commit(types.ENABLE_Y_AXIS, id); 77 | break; 78 | case 'x': 79 | commit(types.ENABLE_NONE_AXIS, id); 80 | break; 81 | case 'y': 82 | commit(types.ENABLE_BOTH_AXIS, id); 83 | break; 84 | case 'none': 85 | commit(types.ENABLE_X_AXIS, id); 86 | break; 87 | } 88 | }, 89 | 90 | changeYLock({commit, state}, {id}) { 91 | switch (state.rects[id].axis) { 92 | case 'both': 93 | commit(types.ENABLE_X_AXIS, id); 94 | break; 95 | case 'x': 96 | commit(types.ENABLE_BOTH_AXIS, id); 97 | break; 98 | case 'y': 99 | commit(types.ENABLE_NONE_AXIS, id); 100 | break; 101 | case 'none': 102 | commit(types.ENABLE_Y_AXIS, id); 103 | break; 104 | } 105 | }, 106 | 107 | changeZToBottom({commit, state}, {id}) { 108 | if (state.rects[id].zIndex === 1) { 109 | return 110 | } 111 | 112 | commit(types.CHANGE_ZINDEX, {id, zIndex: 1}); 113 | 114 | for (let i = 0, l = state.rects.length; i < l; i++) { 115 | if (i !== id) { 116 | if(state.rects[i].zIndex === state.rects.length){ 117 | continue 118 | } 119 | commit(types.CHANGE_ZINDEX, {id: i, zIndex: state.rects[i].zIndex + 1}); 120 | } 121 | } 122 | }, 123 | 124 | changeZToTop({commit, state}, {id}) { 125 | if (state.rects[id].zIndex === state.rects.length) { 126 | return 127 | } 128 | 129 | commit(types.CHANGE_ZINDEX, {id, zIndex: state.rects.length}); 130 | 131 | for (let i = 0, l = state.rects.length; i < l; i++) { 132 | if (i !== id) { 133 | if(state.rects[i].zIndex === 1){ 134 | continue 135 | } 136 | commit(types.CHANGE_ZINDEX, {id: i, zIndex: state.rects[i].zIndex - 1}); 137 | } 138 | } 139 | }, 140 | 141 | setMinWidth({commit}, {id, width}) { 142 | commit(types.CHANGE_MINW, {id, minw:width}); 143 | }, 144 | 145 | setMinHeight({commit}, {id, height}) { 146 | commit(types.CHANGE_MINH, {id, minh:height}); 147 | } 148 | }; 149 | -------------------------------------------------------------------------------- /src/demo/store/modules/rect/getters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | getActive: state => { 3 | for (let i = 0, l = state.rects.length; i < l; i++) { 4 | let rect = state.rects[i]; 5 | 6 | if (rect.active) { 7 | return i; 8 | } 9 | } 10 | return null; 11 | } 12 | } -------------------------------------------------------------------------------- /src/demo/store/modules/rect/index.js: -------------------------------------------------------------------------------- 1 | import actions from './actions'; 2 | import getters from './getters'; 3 | import mutations from './mutations'; 4 | import state from './state'; 5 | 6 | export default { 7 | namespaced: true, 8 | actions, 9 | getters, 10 | mutations, 11 | state 12 | }; 13 | -------------------------------------------------------------------------------- /src/demo/store/modules/rect/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const ENABLE_ACTIVE = 'ENABLE_ACTIVE'; 2 | export const DISABLE_ACTIVE = 'DISABLE_ACTIVE'; 3 | 4 | export const ENABLE_DRAGGABLE = 'ENABLE_DRAGGABLE'; 5 | export const DISABLE_DRAGGABLE = 'DISABLE_DRAGGABLE'; 6 | 7 | export const ENABLE_RESIZABLE = 'ENABLE_RESIZABLE'; 8 | export const DISABLE_RESIZABLE = 'DISABLE_RESIZABLE'; 9 | 10 | export const ENABLE_PARENT_LIMITATION = 'ENABLE_PARENT_LIMITATION'; 11 | export const DISABLE_PARENT_LIMITATION = 'DISABLE_PARENT_LIMITATION'; 12 | 13 | export const ENABLE_SNAP_TO_GRID = 'ENABLE_SNAP_TO_GRID'; 14 | export const DISABLE_SNAP_TO_GRID = 'DISABLE_SNAP_TO_GRID'; 15 | 16 | export const ENABLE_ASPECT = 'ENABLE_ASPECT'; 17 | export const DISABLE_ASPECT = 'DISABLE_ASPECT'; 18 | 19 | export const ENABLE_X_AXIS = 'ENABLE_X_AXIS'; 20 | export const ENABLE_Y_AXIS = 'ENABLE_Y_AXIS'; 21 | export const ENABLE_BOTH_AXIS = 'ENABLE_BOTH_AXIS'; 22 | export const ENABLE_NONE_AXIS = 'ENABLE_NONE_AXIS'; 23 | 24 | export const CHANGE_ZINDEX = 'CHANGE_ZINDEX'; 25 | 26 | export const CHANGE_MINW = 'CHANGE_MINW'; 27 | export const CHANGE_MINH = 'CHANGE_MINH'; 28 | 29 | export const CHANGE_WIDTH = 'CHANGE_WIDTH'; 30 | export const CHANGE_HEIGHT = 'CHANGE_HEIGHT'; 31 | export const CHANGE_TOP = 'CHANGE_TOP'; 32 | export const CHANGE_LEFT = 'CHANGE_LEFT'; 33 | 34 | export default { 35 | ENABLE_ACTIVE, 36 | DISABLE_ACTIVE, 37 | ENABLE_DRAGGABLE, 38 | DISABLE_DRAGGABLE, 39 | ENABLE_RESIZABLE, 40 | DISABLE_RESIZABLE, 41 | ENABLE_PARENT_LIMITATION, 42 | DISABLE_PARENT_LIMITATION, 43 | ENABLE_SNAP_TO_GRID, 44 | DISABLE_SNAP_TO_GRID, 45 | ENABLE_ASPECT, 46 | DISABLE_ASPECT, 47 | ENABLE_X_AXIS, 48 | ENABLE_Y_AXIS, 49 | ENABLE_NONE_AXIS, 50 | ENABLE_BOTH_AXIS, 51 | CHANGE_ZINDEX, 52 | CHANGE_MINW, 53 | CHANGE_MINH, 54 | CHANGE_WIDTH, 55 | CHANGE_HEIGHT, 56 | CHANGE_TOP, 57 | CHANGE_LEFT 58 | } -------------------------------------------------------------------------------- /src/demo/store/modules/rect/mutations.js: -------------------------------------------------------------------------------- 1 | import { 2 | ENABLE_ACTIVE, 3 | DISABLE_ACTIVE, 4 | ENABLE_ASPECT, 5 | DISABLE_ASPECT, 6 | ENABLE_DRAGGABLE, 7 | DISABLE_DRAGGABLE, 8 | ENABLE_RESIZABLE, 9 | DISABLE_RESIZABLE, 10 | ENABLE_PARENT_LIMITATION, 11 | DISABLE_PARENT_LIMITATION, 12 | ENABLE_SNAP_TO_GRID, 13 | DISABLE_SNAP_TO_GRID, 14 | CHANGE_ZINDEX, 15 | ENABLE_BOTH_AXIS, 16 | ENABLE_X_AXIS, 17 | ENABLE_Y_AXIS, 18 | ENABLE_NONE_AXIS, 19 | CHANGE_HEIGHT, 20 | CHANGE_LEFT, 21 | CHANGE_MINH, 22 | CHANGE_MINW, 23 | CHANGE_TOP, 24 | CHANGE_WIDTH 25 | } from './mutation-types'; 26 | 27 | export default { 28 | [ENABLE_ACTIVE](state, id) { 29 | state.rects[id].active = true; 30 | }, 31 | [DISABLE_ACTIVE](state, id) { 32 | state.rects[id].active = false; 33 | }, 34 | 35 | [ENABLE_ASPECT](state, id) { 36 | state.rects[id].aspectRatio = true; 37 | }, 38 | [DISABLE_ASPECT](state, id) { 39 | state.rects[id].aspectRatio = false; 40 | }, 41 | 42 | [ENABLE_DRAGGABLE](state, id) { 43 | state.rects[id].draggable = true; 44 | }, 45 | [DISABLE_DRAGGABLE](state, id) { 46 | state.rects[id].draggable = false; 47 | }, 48 | 49 | [ENABLE_RESIZABLE](state, id) { 50 | state.rects[id].resizable = true; 51 | }, 52 | [DISABLE_RESIZABLE](state, id) { 53 | state.rects[id].resizable = false; 54 | }, 55 | 56 | [ENABLE_SNAP_TO_GRID](state, id) { 57 | state.rects[id].snapToGrid = true; 58 | }, 59 | [DISABLE_SNAP_TO_GRID](state, id) { 60 | state.rects[id].snapToGrid = false; 61 | }, 62 | 63 | [ENABLE_BOTH_AXIS](state, id) { 64 | state.rects[id].axis = 'both'; 65 | }, 66 | [ENABLE_NONE_AXIS](state, id) { 67 | state.rects[id].axis = 'none'; 68 | }, 69 | [ENABLE_X_AXIS](state, id) { 70 | state.rects[id].axis = 'x'; 71 | }, 72 | [ENABLE_Y_AXIS](state, id) { 73 | state.rects[id].axis = 'y'; 74 | }, 75 | 76 | [ENABLE_PARENT_LIMITATION](state, id) { 77 | state.rects[id].parentLim = true; 78 | }, 79 | [DISABLE_PARENT_LIMITATION](state, id) { 80 | state.rects[id].parentLim = false; 81 | }, 82 | 83 | [CHANGE_ZINDEX](state, payload) { 84 | state.rects[payload.id].zIndex = payload.zIndex; 85 | }, 86 | 87 | [CHANGE_HEIGHT](state, payload) { 88 | state.rects[payload.id].height = payload.height; 89 | }, 90 | 91 | [CHANGE_WIDTH](state, payload) { 92 | state.rects[payload.id].width = payload.width; 93 | }, 94 | 95 | [CHANGE_TOP](state, payload) { 96 | state.rects[payload.id].top = payload.top; 97 | }, 98 | 99 | [CHANGE_LEFT](state, payload) { 100 | state.rects[payload.id].left = payload.left; 101 | }, 102 | 103 | [CHANGE_MINH](state, payload) { 104 | 105 | state.rects[payload.id].minh = payload.minh; 106 | }, 107 | 108 | [CHANGE_MINW](state, payload) { 109 | state.rects[payload.id].minw = payload.minw; 110 | } 111 | }; -------------------------------------------------------------------------------- /src/demo/store/modules/rect/state.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'rects': [ 3 | { 4 | 'width': 200, 5 | 'height': 150, 6 | 'top': 10, 7 | 'left': 10, 8 | 'draggable': true, 9 | 'resizable': true, 10 | 'minw': 10, 11 | 'minh': 10, 12 | 'axis': 'both', 13 | 'parentLim': true, 14 | 'snapToGrid': false, 15 | 'aspectRatio': false, 16 | 'zIndex': 1, 17 | 'color': '#EF9A9A', 18 | 'active': false 19 | }, 20 | { 21 | 'width': 200, 22 | 'height': 150, 23 | 'top': 170, 24 | 'left': 220, 25 | 'draggable': true, 26 | 'resizable': true, 27 | 'minw': 10, 28 | 'minh': 10, 29 | 'axis': 'both', 30 | 'parentLim': true, 31 | 'snapToGrid': false, 32 | 'aspectRatio': false, 33 | 'zIndex': 1, 34 | 'color': '#E6C27A', 35 | 'active': false, 36 | 'class': 'box-shaddow' 37 | }, 38 | { 39 | 'width': 200, 40 | 'height': 150, 41 | 'top': 10, 42 | 'left': 220, 43 | 'draggable': true, 44 | 'resizable': true, 45 | 'minw': 10, 46 | 'minh': 10, 47 | 'axis': 'both', 48 | 'parentLim': true, 49 | 'snapToGrid': false, 50 | 'aspectRatio': false, 51 | 'zIndex': 2, 52 | 'color': '#AED581', 53 | 'active': false 54 | }, 55 | { 56 | 'width': 200, 57 | 'height': 150, 58 | 'top': 170, 59 | 'left': 10, 60 | 'draggable': true, 61 | 'resizable': true, 62 | 'minw': 10, 63 | 'minh': 10, 64 | 'axis': 'both', 65 | 'parentLim': true, 66 | 'snapToGrid': false, 67 | 'aspectRatio': false, 68 | 'zIndex': 3, 69 | 'color': '#81D4FA', 70 | 'active': false 71 | } 72 | ] 73 | }; 74 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // Import vue component 2 | import VueDragResize from './components/vue-drag-resize.vue'; 3 | 4 | // Declare install function executed by Vue.use() 5 | export function install(Vue) { 6 | if (install.installed) return; 7 | install.installed = true; 8 | Vue.component('vue-drag-resize', VueDragResize); 9 | } 10 | 11 | // Create module definition for Vue.use() 12 | const plugin = { 13 | install, 14 | }; 15 | 16 | // Auto-install when vue is found (eg. in browser via 61 | 62 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const config = require('./config'); 3 | const CompressionPlugin = require("compression-webpack-plugin"); 4 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 5 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 6 | const { VueLoaderPlugin } = require('vue-loader'); 7 | 8 | let plugins = []; 9 | 10 | plugins.push(new VueLoaderPlugin()); 11 | 12 | if(config.build.sourceMap){ 13 | plugins.push(new webpack.SourceMapDevToolPlugin({ 14 | filename: '[name].map' 15 | })) 16 | } 17 | 18 | plugins.push(new webpack.DefinePlugin({ 19 | 'process.env': { 20 | NODE_ENV: config.build.env 21 | } 22 | })); 23 | 24 | 25 | if(config.build.gzip){ 26 | plugins.push(new CompressionPlugin({ 27 | asset: "[path].gz[query]", 28 | algorithm: "gzip", 29 | test: /\.(js)$/, 30 | threshold: 10240, 31 | minRatio: 0.8 32 | })); 33 | } 34 | 35 | if(config.build.bundleAnalyzerReport){ 36 | plugins.push(new BundleAnalyzerPlugin({ 37 | analyzerMode: 'server', 38 | analyzerHost: '127.0.0.1', 39 | analyzerPort: 8888, 40 | reportFilename: 'report.html', 41 | defaultSizes: 'parsed', 42 | openAnalyzer: true, 43 | generateStatsFile: false, 44 | statsFilename: 'stats.json', 45 | statsOptions: null, 46 | logLevel: 'info' 47 | })) 48 | } 49 | 50 | module.exports = { 51 | resolve: { 52 | alias: { 53 | vue: 'vue/dist/vue.common.js' 54 | } 55 | }, 56 | module: { 57 | rules: [ 58 | 59 | { 60 | test: /\.vue$/, 61 | loader: 'vue-loader' 62 | }, 63 | { 64 | test: /\.js$/, 65 | exclude: /(node_modules|bower_components)/, 66 | use: { 67 | loader: 'babel-loader' 68 | } 69 | }, 70 | { 71 | test: /\.css$/, 72 | use: [ 'style-loader', 'css-loader', 'postcss-loader' ] 73 | } 74 | ] 75 | }, 76 | 77 | devServer: { 78 | contentBase: [config.build.distPath, config.server.assetsPath], 79 | historyApiFallback: true, 80 | noInfo: true, 81 | open: config.server.autoOpenBrowser, 82 | port: config.server.port 83 | }, 84 | 85 | entry: config.entry, 86 | 87 | output: { 88 | path: config.build.distPath, 89 | library: 'VueDragResize', 90 | filename: '[name].js', 91 | libraryTarget: 'umd' 92 | }, 93 | 94 | optimization: { 95 | minimizer: [ 96 | new UglifyJsPlugin({ 97 | sourceMap: false 98 | }) 99 | ] 100 | }, 101 | 102 | plugins: plugins 103 | 104 | }; --------------------------------------------------------------------------------