├── .editorconfig
├── .gitignore
├── README.md
├── babel.config.js
├── dev.md
├── examples
├── App.vue
├── assets
│ ├── bg.jpg
│ ├── btn.png
│ └── logo.png
├── components
│ ├── common
│ │ └── element.vue
│ └── stage.vue
├── favicon.ico
├── index.html
├── lib
│ └── utils
│ │ └── index.js
└── main.js
├── package.json
├── src
├── index.js
└── vue-drag-rotate-resize
│ ├── index.vue
│ ├── rect-box.vue
│ └── utils.js
├── tests
└── unit
│ ├── .eslintrc.js
│ └── example.spec.js
└── vue.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 | /lib
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | # Editor directories and files
16 | .idea
17 | .vscode
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## vue-drag-rotate-resize
2 |
3 |
4 | ### Demo
5 | [demo](https://chenxingyuoo.github.io/vue-drag-rotate-resize)
6 |
7 | ## 安装
8 | ```
9 | npm install @chenxingyu_o/vue-drag-rotate-resize
10 | ```
11 |
12 | ## 注册组件
13 | ```javescript
14 | import VueDragRotateResize from '@chenxingyu_o/vue-drag-rotate-resize'
15 | import '@chenxingyu_o/vue-drag-rotate-resize/lib/vue-drag-rotate-resize.css'
16 | Vue.use(VueDragRotateResize)
17 | ```
18 |
19 | ## 使用组件
20 | ```vue
21 |
22 |
23 |
24 |
42 | content
43 |
44 | o
45 |
46 |
47 |
48 |
49 |
50 |
86 | ```
87 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/dev.md:
--------------------------------------------------------------------------------
1 | # vue-drag-rotate-resize
2 |
3 | ## Project setup
4 | ```
5 | yarn install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | yarn run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | yarn run build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | yarn run lint
21 | ```
22 |
23 | ### Run your unit tests
24 | ```
25 | yarn run test:unit
26 | ```
27 |
--------------------------------------------------------------------------------
/examples/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
26 |
--------------------------------------------------------------------------------
/examples/assets/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenxingyuoo/vue-drag-rotate-resize/23493d93878d2d9947fb0ae0a7ff978f4192ec74/examples/assets/bg.jpg
--------------------------------------------------------------------------------
/examples/assets/btn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenxingyuoo/vue-drag-rotate-resize/23493d93878d2d9947fb0ae0a7ff978f4192ec74/examples/assets/btn.png
--------------------------------------------------------------------------------
/examples/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenxingyuoo/vue-drag-rotate-resize/23493d93878d2d9947fb0ae0a7ff978f4192ec74/examples/assets/logo.png
--------------------------------------------------------------------------------
/examples/components/common/element.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
![]()
7 |
8 |
9 |
10 |
56 |
57 |
66 |
--------------------------------------------------------------------------------
/examples/components/stage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
236 |
237 |
256 |
--------------------------------------------------------------------------------
/examples/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenxingyuoo/vue-drag-rotate-resize/23493d93878d2d9947fb0ae0a7ff978f4192ec74/examples/favicon.ico
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-drag-rotate-resize
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/lib/utils/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 获取2个平面的宽比、高比
3 | * @param {object} originRect 原宽高对象
4 | * @param {object} targetRect 目标宽高对象
5 | * @return {object} 宽高比
6 | * @example
7 | * getRect2WHRatio({width: 100, height: 100}, {width: 200, height: 200})
8 | * // => {heightRatio: 2,maxRatio: 2, minRatio: 2, widthRatio: 2}
9 | */
10 | export const getRect2WHRatio = (originRect, targetRect) => {
11 | const widthRatio = targetRect.width / originRect.width
12 | const heightRatio = targetRect.height / originRect.height
13 | return {
14 | widthRatio: widthRatio,
15 | heightRatio: heightRatio,
16 | maxRatio: Math.max(widthRatio, heightRatio),
17 | minRatio: Math.min(widthRatio, heightRatio)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import VueDragRotateResize from '../src/index'
4 | Vue.use(VueDragRotateResize)
5 |
6 | Vue.config.productionTip = false
7 |
8 | new Vue({
9 | render: h => h(App)
10 | }).$mount('#app')
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@chenxingyu_o/vue-drag-rotate-resize",
3 | "version": "1.0.0",
4 | "author": "chenxignyuoo",
5 | "description": "a vue2 component. drag-rotate-resize",
6 | "main": "lib/vue-drag-rotate-resize.common.js",
7 | "scripts": {
8 | "serve": "vue-cli-service serve",
9 | "build": "vue-cli-service build ./src/index.js --dest lib --target lib --name vue-drag-rotate-resize",
10 | "build-demo": "vue-cli-service build",
11 | "lint": "vue-cli-service lint",
12 | "test:unit": "vue-cli-service test:unit"
13 | },
14 | "files": [
15 | "lib",
16 | "src"
17 | ],
18 | "keywords": [
19 | "vue",
20 | "drag",
21 | "resize",
22 | "rotate",
23 | "draggable",
24 | "resizable",
25 | "rotateable"
26 | ],
27 | "repository": {
28 | "type": "git",
29 | "url": "git+https://github.com/chenxingyuoo/vue-drag-rotate-resize.git"
30 | },
31 | "dependencies": {
32 | "core-js": "^2.6.5",
33 | "vue": "^2.6.10"
34 | },
35 | "devDependencies": {
36 | "@vue/cli-plugin-babel": "^3.0.1",
37 | "@vue/cli-plugin-eslint": "^3.0.1",
38 | "@vue/cli-plugin-unit-jest": "^3.0.1",
39 | "@vue/cli-service": "^3.0.1",
40 | "@vue/eslint-config-standard": "^4.0.0",
41 | "@vue/test-utils": "1.0.0-beta.29",
42 | "babel-core": "7.0.0-bridge.0",
43 | "babel-eslint": "^10.0.1",
44 | "babel-jest": "^23.6.0",
45 | "eslint": "^5.16.0",
46 | "eslint-plugin-vue": "^5.0.0",
47 | "less": "^3.0.4",
48 | "less-loader": "^4.1.0",
49 | "vue-template-compiler": "^2.6.10"
50 | },
51 | "eslintConfig": {
52 | "root": true,
53 | "env": {
54 | "node": true
55 | },
56 | "extends": [
57 | "plugin:vue/essential",
58 | "@vue/standard"
59 | ],
60 | "rules": {},
61 | "parserOptions": {
62 | "parser": "babel-eslint"
63 | }
64 | },
65 | "postcss": {
66 | "plugins": {
67 | "autoprefixer": {}
68 | }
69 | },
70 | "browserslist": [
71 | "> 1%",
72 | "last 2 versions"
73 | ],
74 | "jest": {
75 | "moduleFileExtensions": [
76 | "js",
77 | "jsx",
78 | "json",
79 | "vue"
80 | ],
81 | "transform": {
82 | "^.+\\.vue$": "vue-jest",
83 | ".+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$": "jest-transform-stub",
84 | "^.+\\.jsx?$": "babel-jest"
85 | },
86 | "transformIgnorePatterns": [
87 | "/node_modules/"
88 | ],
89 | "moduleNameMapper": {
90 | "^@/(.*)$": "/src/$1"
91 | },
92 | "snapshotSerializers": [
93 | "jest-serializer-vue"
94 | ],
95 | "testMatch": [
96 | "**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"
97 | ],
98 | "testURL": "http://localhost/",
99 | "watchPlugins": [
100 | "jest-watch-typeahead/filename",
101 | "jest-watch-typeahead/testname"
102 | ]
103 | },
104 | "license": "MIT"
105 | }
106 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import DragRotateResize from './vue-drag-rotate-resize/index.vue'
2 |
3 | DragRotateResize.install = function (Vue) {
4 | Vue.component(DragRotateResize.name, DragRotateResize)
5 | }
6 |
7 | /* istanbul ignore if */
8 | if (typeof window !== 'undefined' && window.Vue) {
9 | DragRotateResize.install(window.Vue)
10 | }
11 |
12 | export default DragRotateResize
13 |
--------------------------------------------------------------------------------
/src/vue-drag-rotate-resize/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
212 |
--------------------------------------------------------------------------------
/src/vue-drag-rotate-resize/rect-box.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
startResize(e, zoomableMap[item])" :style="[{cursor: getCursorStyle(item)}, reverseScaleStyle]" :key="i">
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
254 |
255 |
356 |
--------------------------------------------------------------------------------
/src/vue-drag-rotate-resize/utils.js:
--------------------------------------------------------------------------------
1 | export const getLength = (x, y) => Math.sqrt(x * x + y * y)
2 |
3 | export const getAngle = ({ x: x1, y: y1 }, { x: x2, y: y2 }) => {
4 | const dot = x1 * x2 + y1 * y2
5 | const det = x1 * y2 - y1 * x2
6 | const angle = Math.atan2(det, dot) / Math.PI * 180
7 | return (angle + 360) % 360
8 | }
9 |
10 | export const degToRadian = (deg) => deg * Math.PI / 180
11 |
12 | const cos = (deg) => Math.cos(degToRadian(deg))
13 | const sin = (deg) => Math.sin(degToRadian(deg))
14 |
15 | const setWidthAndDeltaW = (width, deltaW, minWidth) => {
16 | const expectedWidth = width + deltaW
17 | if (expectedWidth > minWidth) {
18 | width = expectedWidth
19 | } else {
20 | deltaW = minWidth - width
21 | width = minWidth
22 | }
23 | return { width, deltaW }
24 | }
25 |
26 | const setHeightAndDeltaH = (height, deltaH, minHeight) => {
27 | const expectedHeight = height + deltaH
28 | if (expectedHeight > minHeight) {
29 | height = expectedHeight
30 | } else {
31 | deltaH = minHeight - height
32 | height = minHeight
33 | }
34 | return { height, deltaH }
35 | }
36 |
37 | export const getNewStyle = (type, rect, deltaW, deltaH, ratio, minWidth, minHeight) => {
38 | let { width, height, centerX, centerY, rotateAngle } = rect
39 | const widthFlag = width < 0 ? -1 : 1
40 | const heightFlag = height < 0 ? -1 : 1
41 | width = Math.abs(width)
42 | height = Math.abs(height)
43 | switch (type) {
44 | case 'r': {
45 | const widthAndDeltaW = setWidthAndDeltaW(width, deltaW, minWidth)
46 | width = widthAndDeltaW.width
47 | deltaW = widthAndDeltaW.deltaW
48 | if (ratio) {
49 | deltaH = deltaW / ratio
50 | height = width / ratio
51 | // 左上角固定
52 | centerX += deltaW / 2 * cos(rotateAngle) - deltaH / 2 * sin(rotateAngle)
53 | centerY += deltaW / 2 * sin(rotateAngle) + deltaH / 2 * cos(rotateAngle)
54 | } else {
55 | // 左边固定
56 | centerX += deltaW / 2 * cos(rotateAngle)
57 | centerY += deltaW / 2 * sin(rotateAngle)
58 | }
59 | break
60 | }
61 | case 'tr': {
62 | deltaH = -deltaH
63 | const widthAndDeltaW = setWidthAndDeltaW(width, deltaW, minWidth)
64 | width = widthAndDeltaW.width
65 | deltaW = widthAndDeltaW.deltaW
66 | const heightAndDeltaH = setHeightAndDeltaH(height, deltaH, minHeight)
67 | height = heightAndDeltaH.height
68 | deltaH = heightAndDeltaH.deltaH
69 | if (ratio) {
70 | deltaW = deltaH * ratio
71 | width = height * ratio
72 | }
73 | centerX += deltaW / 2 * cos(rotateAngle) + deltaH / 2 * sin(rotateAngle)
74 | centerY += deltaW / 2 * sin(rotateAngle) - deltaH / 2 * cos(rotateAngle)
75 | break
76 | }
77 | case 'br': {
78 | const widthAndDeltaW = setWidthAndDeltaW(width, deltaW, minWidth)
79 | width = widthAndDeltaW.width
80 | deltaW = widthAndDeltaW.deltaW
81 | const heightAndDeltaH = setHeightAndDeltaH(height, deltaH, minHeight)
82 | height = heightAndDeltaH.height
83 | deltaH = heightAndDeltaH.deltaH
84 | if (ratio) {
85 | deltaW = deltaH * ratio
86 | width = height * ratio
87 | }
88 | centerX += deltaW / 2 * cos(rotateAngle) - deltaH / 2 * sin(rotateAngle)
89 | centerY += deltaW / 2 * sin(rotateAngle) + deltaH / 2 * cos(rotateAngle)
90 | break
91 | }
92 | case 'b': {
93 | const heightAndDeltaH = setHeightAndDeltaH(height, deltaH, minHeight)
94 | height = heightAndDeltaH.height
95 | deltaH = heightAndDeltaH.deltaH
96 | if (ratio) {
97 | deltaW = deltaH * ratio
98 | width = height * ratio
99 | // 左上角固定
100 | centerX += deltaW / 2 * cos(rotateAngle) - deltaH / 2 * sin(rotateAngle)
101 | centerY += deltaW / 2 * sin(rotateAngle) + deltaH / 2 * cos(rotateAngle)
102 | } else {
103 | // 上边固定
104 | centerX -= deltaH / 2 * sin(rotateAngle)
105 | centerY += deltaH / 2 * cos(rotateAngle)
106 | }
107 | break
108 | }
109 | case 'bl': {
110 | deltaW = -deltaW
111 | const widthAndDeltaW = setWidthAndDeltaW(width, deltaW, minWidth)
112 | width = widthAndDeltaW.width
113 | deltaW = widthAndDeltaW.deltaW
114 | const heightAndDeltaH = setHeightAndDeltaH(height, deltaH, minHeight)
115 | height = heightAndDeltaH.height
116 | deltaH = heightAndDeltaH.deltaH
117 | if (ratio) {
118 | height = width / ratio
119 | deltaH = deltaW / ratio
120 | }
121 | centerX -= deltaW / 2 * cos(rotateAngle) + deltaH / 2 * sin(rotateAngle)
122 | centerY -= deltaW / 2 * sin(rotateAngle) - deltaH / 2 * cos(rotateAngle)
123 | break
124 | }
125 | case 'l': {
126 | deltaW = -deltaW
127 | const widthAndDeltaW = setWidthAndDeltaW(width, deltaW, minWidth)
128 | width = widthAndDeltaW.width
129 | deltaW = widthAndDeltaW.deltaW
130 | if (ratio) {
131 | height = width / ratio
132 | deltaH = deltaW / ratio
133 | // 右上角固定
134 | centerX -= deltaW / 2 * cos(rotateAngle) + deltaH / 2 * sin(rotateAngle)
135 | centerY -= deltaW / 2 * sin(rotateAngle) - deltaH / 2 * cos(rotateAngle)
136 | } else {
137 | // 右边固定
138 | centerX -= deltaW / 2 * cos(rotateAngle)
139 | centerY -= deltaW / 2 * sin(rotateAngle)
140 | }
141 | break
142 | }
143 | case 'tl': {
144 | deltaW = -deltaW
145 | deltaH = -deltaH
146 | const widthAndDeltaW = setWidthAndDeltaW(width, deltaW, minWidth)
147 | width = widthAndDeltaW.width
148 | deltaW = widthAndDeltaW.deltaW
149 | const heightAndDeltaH = setHeightAndDeltaH(height, deltaH, minHeight)
150 | height = heightAndDeltaH.height
151 | deltaH = heightAndDeltaH.deltaH
152 | if (ratio) {
153 | width = height * ratio
154 | deltaW = deltaH * ratio
155 | }
156 | centerX -= deltaW / 2 * cos(rotateAngle) - deltaH / 2 * sin(rotateAngle)
157 | centerY -= deltaW / 2 * sin(rotateAngle) + deltaH / 2 * cos(rotateAngle)
158 | break
159 | }
160 | case 't': {
161 | deltaH = -deltaH
162 | const heightAndDeltaH = setHeightAndDeltaH(height, deltaH, minHeight)
163 | height = heightAndDeltaH.height
164 | deltaH = heightAndDeltaH.deltaH
165 | if (ratio) {
166 | width = height * ratio
167 | deltaW = deltaH * ratio
168 | // 左下角固定
169 | centerX += deltaW / 2 * cos(rotateAngle) + deltaH / 2 * sin(rotateAngle)
170 | centerY += deltaW / 2 * sin(rotateAngle) - deltaH / 2 * cos(rotateAngle)
171 | } else {
172 | centerX += deltaH / 2 * sin(rotateAngle)
173 | centerY -= deltaH / 2 * cos(rotateAngle)
174 | }
175 | break
176 | }
177 | }
178 |
179 | return {
180 | position: {
181 | centerX,
182 | centerY
183 | },
184 | size: {
185 | width: width * widthFlag,
186 | height: height * heightFlag
187 | }
188 | }
189 | }
190 |
191 | const cursorStartMap = { n: 0, ne: 1, e: 2, se: 3, s: 4, sw: 5, w: 6, nw: 7 }
192 | // const cursorDirectionArray = [ 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw' ]
193 | const cursorDirectionArray = ['ns', 'nesw', 'ew', 'nwse', 'ns', 'nesw', 'ew', 'nwse']
194 | const cursorMap = { 0: 0, 1: 1, 2: 2, 3: 2, 4: 3, 5: 4, 6: 4, 7: 5, 8: 6, 9: 6, 10: 7, 11: 8 }
195 | export const getCursor = (rotateAngle, d) => {
196 | const increment = cursorMap[ Math.floor(rotateAngle / 30) ]
197 | const index = cursorStartMap[ d ]
198 | const newIndex = (index + increment) % 8
199 | return cursorDirectionArray[ newIndex ]
200 | }
201 |
202 | export const centerToTL = ({ centerX, centerY, width, height, rotateAngle }) => ({
203 | top: centerY - height / 2,
204 | left: centerX - width / 2,
205 | width,
206 | height,
207 | rotateAngle
208 | })
209 |
210 | export const tLToCenter = ({ top, left, width, height, rotateAngle }) => ({
211 | position: {
212 | centerX: left + width / 2,
213 | centerY: top + height / 2
214 | },
215 | size: {
216 | width,
217 | height
218 | },
219 | transform: {
220 | rotateAngle
221 | }
222 | })
223 |
224 | /**
225 | * 获取一个矩形的中心点
226 | * @param {object} rect 矩形宽高对象
227 | * @return {object} 中心点坐标
228 | * @example
229 | * getRectCenter({width: 100, height: 100})
230 | * // => {centerX: 50, centerY: 50}
231 | */
232 | export const getRectCenter = (rect) => {
233 | return {
234 | centerX: rect.width / 2,
235 | centerY: rect.height / 2
236 | }
237 | }
238 |
239 | /**
240 | * 获取2个矩形的中心点偏移值
241 | * @param {object} originRect 原宽高对象
242 | * @param {object} targetRect 目标宽高对象
243 | * @example
244 | * getRect2CenterTranslate({width: 100, height: 100}, {width: 200, height: 200})
245 | * // => {left: 50, top: 50}
246 | */
247 | export const getRect2CenterTranslate = (originRect, targetRect) => {
248 | const targetCenter = getRectCenter(targetRect)
249 | const originCenter = getRectCenter(originRect)
250 | return {
251 | top: targetCenter.centerY - originCenter.centerY,
252 | left: targetCenter.centerX - originCenter.centerX
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/tests/unit/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | jest: true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/tests/unit/example.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount } from '@vue/test-utils'
2 | import HelloWorld from '@/components/HelloWorld.vue'
3 |
4 | describe('HelloWorld.vue', () => {
5 | it('renders props.msg when passed', () => {
6 | const msg = 'new message'
7 | const wrapper = shallowMount(HelloWorld, {
8 | propsData: { msg }
9 | })
10 | expect(wrapper.text()).toMatch(msg)
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | pages: {
3 | index: {
4 | entry: 'examples/main.js',
5 | template: 'examples/index.html',
6 | filename: 'index.html'
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------