├── static
├── .gitkeep
└── dragger.js
├── .eslintignore
├── test
├── unit
│ ├── setup.js
│ ├── .eslintrc
│ ├── specs
│ │ └── HelloWorld.spec.js
│ └── jest.conf.js
└── e2e
│ ├── specs
│ └── test.js
│ ├── custom-assertions
│ └── elementCount.js
│ ├── nightwatch.conf.js
│ └── runner.js
├── imgs
└── attribute.png
├── src
├── assets
│ ├── logo.png
│ └── css
│ │ └── dragger.css
├── router
│ └── index.js
├── App.vue
├── main.js
└── components
│ └── HelloWorld.vue
├── config
├── prod.env.js
├── test.env.js
├── dev.env.js
└── index.js
├── .editorconfig
├── .gitignore
├── .postcssrc.js
├── .babelrc
├── index.html
├── .eslintrc.js
├── package.json
└── README.md
/static/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /config/
3 | /dist/
4 | /*.js
5 | /test/unit/coverage/
6 |
--------------------------------------------------------------------------------
/test/unit/setup.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | Vue.config.productionTip = false
4 |
--------------------------------------------------------------------------------
/imgs/attribute.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/munan2/box-dragger/HEAD/imgs/attribute.png
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/munan2/box-dragger/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/test/unit/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "jest": true
4 | },
5 | "globals": {
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/config/test.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const devEnv = require('./dev.env')
4 |
5 | module.exports = merge(devEnv, {
6 | NODE_ENV: '"testing"'
7 | })
8 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const prodEnv = require('./prod.env')
4 |
5 | module.exports = merge(prodEnv, {
6 | NODE_ENV: '"development"'
7 | })
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | /dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | /test/unit/coverage/
8 | /test/e2e/reports/
9 | selenium-debug.log
10 |
11 | # Editor directories and files
12 | .idea
13 | .vscode
14 | *.suo
15 | *.ntvs*
16 | *.njsproj
17 | *.sln
18 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | "postcss-import": {},
6 | "postcss-url": {},
7 | // to edit target browsers: use "browserslist" field in package.json
8 | "autoprefixer": {}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import HelloWorld from '@/components/HelloWorld'
4 |
5 | Vue.use(Router)
6 |
7 | export default new Router({
8 | routes: [
9 | {
10 | path: '/',
11 | name: 'HelloWorld',
12 | component: HelloWorld
13 | }
14 | ]
15 | })
16 |
--------------------------------------------------------------------------------
/test/unit/specs/HelloWorld.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import HelloWorld from '@/components/HelloWorld'
3 |
4 | describe('HelloWorld.vue', () => {
5 | it('should render correct contents', () => {
6 | const Constructor = Vue.extend(HelloWorld)
7 | const vm = new Constructor().$mount()
8 | expect(vm.$el.querySelector('.hello h1').textContent)
9 | .toEqual('Welcome to Your Vue.js App')
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-vue-jsx", "transform-runtime"],
12 | "env": {
13 | "test": {
14 | "presets": ["env", "stage-2"],
15 | "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 | 拖拽
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
28 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: main.js
3 | * @Author: zy
4 | * @LastEditors: zy
5 | * @Date: 2019-04-02 17:50:38
6 | * @LastEditTime: 2019-04-08 14:32:08
7 | */
8 | // The Vue build version to load with the `import` command
9 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
10 | import Vue from 'vue'
11 | import App from './App'
12 | import router from './router'
13 | import './assets/css/dragger.css'
14 | Vue.config.productionTip = false
15 |
16 | /* eslint-disable no-new */
17 | new Vue({
18 | el: '#app',
19 | router,
20 | components: { App },
21 | template: ''
22 | })
23 |
--------------------------------------------------------------------------------
/test/e2e/specs/test.js:
--------------------------------------------------------------------------------
1 | // For authoring Nightwatch tests, see
2 | // http://nightwatchjs.org/guide#usage
3 |
4 | module.exports = {
5 | 'default e2e tests': function (browser) {
6 | // automatically uses dev Server port from /config.index.js
7 | // default: http://localhost:8080
8 | // see nightwatch.conf.js
9 | const devServer = browser.globals.devServerURL
10 |
11 | browser
12 | .url(devServer)
13 | .waitForElementVisible('#app', 5000)
14 | .assert.elementPresent('.hello')
15 | .assert.containsText('h1', 'Welcome to Your Vue.js App')
16 | .assert.elementCount('img', 1)
17 | .end()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test/unit/jest.conf.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | rootDir: path.resolve(__dirname, '../../'),
5 | moduleFileExtensions: [
6 | 'js',
7 | 'json',
8 | 'vue'
9 | ],
10 | moduleNameMapper: {
11 | '^@/(.*)$': '/src/$1'
12 | },
13 | transform: {
14 | '^.+\\.js$': '/node_modules/babel-jest',
15 | '.*\\.(vue)$': '/node_modules/vue-jest'
16 | },
17 | testPathIgnorePatterns: [
18 | '/test/e2e'
19 | ],
20 | snapshotSerializers: ['/node_modules/jest-serializer-vue'],
21 | setupFiles: ['/test/unit/setup'],
22 | mapCoverage: true,
23 | coverageDirectory: '/test/unit/coverage',
24 | collectCoverageFrom: [
25 | 'src/**/*.{js,vue}',
26 | '!src/main.js',
27 | '!src/router/index.js',
28 | '!**/node_modules/**'
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/test/e2e/custom-assertions/elementCount.js:
--------------------------------------------------------------------------------
1 | // A custom Nightwatch assertion.
2 | // The assertion name is the filename.
3 | // Example usage:
4 | //
5 | // browser.assert.elementCount(selector, count)
6 | //
7 | // For more information on custom assertions see:
8 | // http://nightwatchjs.org/guide#writing-custom-assertions
9 |
10 | exports.assertion = function (selector, count) {
11 | this.message = 'Testing if element <' + selector + '> has count: ' + count
12 | this.expected = count
13 | this.pass = function (val) {
14 | return val === this.expected
15 | }
16 | this.value = function (res) {
17 | return res.value
18 | }
19 | this.command = function (cb) {
20 | var self = this
21 | return this.api.execute(function (selector) {
22 | return document.querySelectorAll(selector).length
23 | }, [selector], function (res) {
24 | cb.call(self, res)
25 | })
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // https://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parserOptions: {
6 | parser: 'babel-eslint'
7 | },
8 | env: {
9 | browser: true,
10 | },
11 | extends: [
12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
14 | 'plugin:vue/essential',
15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md
16 | 'standard'
17 | ],
18 | // required to lint *.vue files
19 | plugins: [
20 | 'vue'
21 | ],
22 | // add your custom rules here
23 | rules: {
24 | // allow async-await
25 | 'generator-star-spacing': 'off',
26 | // allow debugger during development
27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/test/e2e/nightwatch.conf.js:
--------------------------------------------------------------------------------
1 | require('babel-register')
2 | var config = require('../../config')
3 |
4 | // http://nightwatchjs.org/gettingstarted#settings-file
5 | module.exports = {
6 | src_folders: ['test/e2e/specs'],
7 | output_folder: 'test/e2e/reports',
8 | custom_assertions_path: ['test/e2e/custom-assertions'],
9 |
10 | selenium: {
11 | start_process: true,
12 | server_path: require('selenium-server').path,
13 | host: '127.0.0.1',
14 | port: 4444,
15 | cli_args: {
16 | 'webdriver.chrome.driver': require('chromedriver').path
17 | }
18 | },
19 |
20 | test_settings: {
21 | default: {
22 | selenium_port: 4444,
23 | selenium_host: 'localhost',
24 | silent: true,
25 | globals: {
26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
27 | }
28 | },
29 |
30 | chrome: {
31 | desiredCapabilities: {
32 | browserName: 'chrome',
33 | javascriptEnabled: true,
34 | acceptSslCerts: true
35 | }
36 | },
37 |
38 | firefox: {
39 | desiredCapabilities: {
40 | browserName: 'firefox',
41 | javascriptEnabled: true,
42 | acceptSslCerts: true
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/test/e2e/runner.js:
--------------------------------------------------------------------------------
1 | // 1. start the dev server using production config
2 | process.env.NODE_ENV = 'testing'
3 |
4 | const webpack = require('webpack')
5 | const DevServer = require('webpack-dev-server')
6 |
7 | const webpackConfig = require('../../build/webpack.prod.conf')
8 | const devConfigPromise = require('../../build/webpack.dev.conf')
9 |
10 | let server
11 |
12 | devConfigPromise.then(devConfig => {
13 | const devServerOptions = devConfig.devServer
14 | const compiler = webpack(webpackConfig)
15 | server = new DevServer(compiler, devServerOptions)
16 | const port = devServerOptions.port
17 | const host = devServerOptions.host
18 | return server.listen(port, host)
19 | })
20 | .then(() => {
21 | // 2. run the nightwatch test suite against it
22 | // to run in additional browsers:
23 | // 1. add an entry in test/e2e/nightwatch.conf.js under "test_settings"
24 | // 2. add it to the --env flag below
25 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
26 | // For more information on Nightwatch's config file, see
27 | // http://nightwatchjs.org/guide#settings-file
28 | let opts = process.argv.slice(2)
29 | if (opts.indexOf('--config') === -1) {
30 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])
31 | }
32 | if (opts.indexOf('--env') === -1) {
33 | opts = opts.concat(['--env', 'chrome'])
34 | }
35 |
36 | const spawn = require('cross-spawn')
37 | const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
38 |
39 | runner.on('exit', function (code) {
40 | server.close()
41 | process.exit(code)
42 | })
43 |
44 | runner.on('error', function (err) {
45 | server.close()
46 | throw err
47 | })
48 | })
49 |
--------------------------------------------------------------------------------
/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
哈哈
12 |
13 |

14 |
15 |
16 |
17 |
18 |
51 |
52 |
53 |
111 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Template version: 1.3.1
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 |
5 | const path = require('path')
6 |
7 | module.exports = {
8 | dev: {
9 |
10 | // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/',
13 | proxyTable: {},
14 |
15 | // Various Dev Server settings
16 | host: 'localhost', // can be overwritten by process.env.HOST
17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
18 | autoOpenBrowser: false,
19 | errorOverlay: true,
20 | notifyOnErrors: true,
21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
22 |
23 | // Use Eslint Loader?
24 | // If true, your code will be linted during bundling and
25 | // linting errors and warnings will be shown in the console.
26 | useEslint: true,
27 | // If true, eslint errors and warnings will also be shown in the error overlay
28 | // in the browser.
29 | showEslintErrorsInOverlay: false,
30 |
31 | /**
32 | * Source Maps
33 | */
34 |
35 | // https://webpack.js.org/configuration/devtool/#development
36 | devtool: 'cheap-module-eval-source-map',
37 |
38 | // If you have problems debugging vue-files in devtools,
39 | // set this to false - it *may* help
40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
41 | cacheBusting: true,
42 |
43 | cssSourceMap: true
44 | },
45 |
46 | build: {
47 | // Template for index.html
48 | index: path.resolve(__dirname, '../dist/index.html'),
49 |
50 | // Paths
51 | assetsRoot: path.resolve(__dirname, '../dist'),
52 | assetsSubDirectory: 'static',
53 | assetsPublicPath: '/',
54 |
55 | /**
56 | * Source Maps
57 | */
58 |
59 | productionSourceMap: true,
60 | // https://webpack.js.org/configuration/devtool/#production
61 | devtool: '#source-map',
62 |
63 | // Gzip off by default as many popular static hosts such as
64 | // Surge or Netlify already gzip all static assets for you.
65 | // Before setting to `true`, make sure to:
66 | // npm install --save-dev compression-webpack-plugin
67 | productionGzip: false,
68 | productionGzipExtensions: ['js', 'css'],
69 |
70 | // Run the build command with an extra argument to
71 | // View the bundle analyzer report after build finishes:
72 | // `npm run build --report`
73 | // Set to `true` or `false` to always turn it on or off
74 | bundleAnalyzerReport: process.env.npm_config_report
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/assets/css/dragger.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | }
5 | #pannel {
6 | position: absolute;
7 | }
8 | .dragger-rotate-point {
9 | position: absolute;
10 | left: 50%;
11 | width: 5px;
12 | height: 5px;
13 | margin-left: -3px;
14 | border-radius: 5px;
15 | border: 1px solid #000;
16 | top: -30px;
17 | cursor: url(http://hr.xesv5.com/docs/imgs/mouserotate.ico) 10 10, default;
18 | }
19 | .dragger-rotate-point span {
20 | position: absolute;
21 | top: 6px;
22 | left: -6px;
23 | }
24 | .dragger-rotate-point:after {
25 | content: '';
26 | display: inline-block;
27 | height: 23px;
28 | width: 1px;
29 | position: absolute;
30 | left: 2px;
31 | top: 6px;
32 | background: #000;
33 | cursor: auto;
34 | }
35 | .dragger-rotate-angle {
36 | position: absolute;
37 | left: 52%;
38 | top: -20px;
39 | }
40 | .dragger-position {
41 | position: absolute;
42 | right: 0;
43 | padding: 0 5px;
44 | background: #fafafa;
45 | border: 1px solid #ccc;
46 | bottom: -35px;
47 | border-radius: 2px;
48 | }
49 | .dragger-base-piont {
50 | position: absolute;
51 | width: 5px;
52 | height: 5px;
53 | border: 1px solid #000;
54 | border-radius: 5px;
55 | background: #fff;
56 | }
57 | .dragger-zoom1 {
58 | left: -3px;
59 | top: -3px;
60 | cursor: nw-resize;
61 | }
62 | .dragger-zoom2 {
63 | left: -3px;
64 | bottom: -3px;
65 | cursor: sw-resize;
66 | }
67 | .dragger-zoom3 {
68 | right: -3px;
69 | top: -3px;
70 | cursor: ne-resize;
71 | }
72 | .dragger-zoom4 {
73 | right: -3px;
74 | bottom: -3px;
75 | cursor: se-resize;
76 | }
77 | .dragger-pull5 {
78 | left: 50%;
79 | margin-left: -3px;
80 | top: -3px;
81 | cursor: row-resize;
82 | }
83 | .dragger-pull6 {
84 | top: 50%;
85 | margin-top: -3px;
86 | left: -3px;
87 | cursor: col-resize;
88 | }
89 | .dragger-pull7 {
90 | left: 50%;
91 | margin-left: -3px;
92 | bottom: -3px;
93 | cursor: row-resize;
94 | }
95 | .dragger-pull8 {
96 | top: 50%;
97 | margin-top: -3px;
98 | right: -3px;
99 | cursor: col-resize;
100 | }
101 | .dragger-base-line {
102 | position: absolute;
103 | }
104 | .dragger-line1 {
105 | width: 1px;
106 | height: 100%;
107 | background: #000;
108 | left: 0;
109 | top: 0;
110 | }
111 | .dragger-line2 {
112 | height: 1px;
113 | width: 100%;
114 | left: 0;
115 | background: #000;
116 | top: 0;
117 | }
118 | .dragger-line3 {
119 | width: 1px;
120 | height: 100%;
121 | background: #000;
122 | right: 0;
123 | top: 0;
124 | }
125 | .dragger-line4 {
126 | height: 1px;
127 | width: 100%;
128 | left: 0;
129 | background: #000;
130 | bottom: 0;
131 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "y",
3 | "version": "1.0.0",
4 | "description": "y",
5 | "author": "y",
6 | "private": true,
7 | "scripts": {
8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 | "start": "npm run dev",
10 | "unit": "jest --config test/unit/jest.conf.js --coverage",
11 | "e2e": "node test/e2e/runner.js",
12 | "test": "npm run unit && npm run e2e",
13 | "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
14 | "build": "node build/build.js"
15 | },
16 | "dependencies": {
17 | "style-loader": "^0.23.1",
18 | "vue": "^2.5.2",
19 | "vue-router": "^3.0.1"
20 | },
21 | "devDependencies": {
22 | "autoprefixer": "^7.1.2",
23 | "babel-core": "^6.22.1",
24 | "babel-eslint": "^8.2.1",
25 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
26 | "babel-jest": "^21.0.2",
27 | "babel-loader": "^7.1.1",
28 | "babel-plugin-dynamic-import-node": "^1.2.0",
29 | "babel-plugin-syntax-jsx": "^6.18.0",
30 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
31 | "babel-plugin-transform-runtime": "^6.22.0",
32 | "babel-plugin-transform-vue-jsx": "^3.5.0",
33 | "babel-preset-env": "^1.3.2",
34 | "babel-preset-stage-2": "^6.22.0",
35 | "babel-register": "^6.22.0",
36 | "chalk": "^2.0.1",
37 | "chromedriver": "^2.27.2",
38 | "copy-webpack-plugin": "^4.0.1",
39 | "cross-spawn": "^5.0.1",
40 | "css-loader": "^0.28.11",
41 | "eslint": "^4.15.0",
42 | "eslint-config-standard": "^10.2.1",
43 | "eslint-friendly-formatter": "^3.0.0",
44 | "eslint-loader": "^1.7.1",
45 | "eslint-plugin-import": "^2.7.0",
46 | "eslint-plugin-node": "^5.2.0",
47 | "eslint-plugin-promise": "^3.4.0",
48 | "eslint-plugin-standard": "^3.0.1",
49 | "eslint-plugin-vue": "^4.0.0",
50 | "extract-text-webpack-plugin": "^3.0.0",
51 | "file-loader": "^1.1.4",
52 | "friendly-errors-webpack-plugin": "^1.6.1",
53 | "html-webpack-plugin": "^2.30.1",
54 | "jest": "^22.0.4",
55 | "jest-serializer-vue": "^0.3.0",
56 | "nightwatch": "^0.9.12",
57 | "node-notifier": "^5.1.2",
58 | "optimize-css-assets-webpack-plugin": "^3.2.0",
59 | "ora": "^1.2.0",
60 | "portfinder": "^1.0.13",
61 | "postcss-import": "^11.0.0",
62 | "postcss-loader": "^2.0.8",
63 | "postcss-url": "^7.2.1",
64 | "rimraf": "^2.6.0",
65 | "selenium-server": "^3.0.1",
66 | "semver": "^5.3.0",
67 | "shelljs": "^0.7.6",
68 | "uglifyjs-webpack-plugin": "^1.1.1",
69 | "url-loader": "^0.5.8",
70 | "vue-jest": "^1.0.2",
71 | "vue-loader": "^13.3.0",
72 | "vue-style-loader": "^3.0.1",
73 | "vue-template-compiler": "^2.5.2",
74 | "webpack": "^3.6.0",
75 | "webpack-bundle-analyzer": "^2.9.0",
76 | "webpack-dev-server": "^2.9.1",
77 | "webpack-merge": "^4.1.0"
78 | },
79 | "engines": {
80 | "node": ">= 6.0.0",
81 | "npm": ">= 3.0.0"
82 | },
83 | "browserslist": [
84 | "> 1%",
85 | "last 2 versions",
86 | "not ie <= 8"
87 | ]
88 | }
89 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 效果图如下:
2 | 
3 | github地址如下:
4 | [github地址](https://github.com/munan2/boxDragger)
5 | ## 使用方法
6 | 引入js和对应的css
7 |
8 | ```js
9 | import Drag from '../../static/dragger.js'
10 | import './assets/css/dragger.css'
11 | ```
12 | 之后,实例化
13 |
14 | ```js
15 | new Drag({
16 | id: 'box-dragger',
17 | showAngle: true,
18 | isScale: false,
19 | showBorder: false
20 | })
21 | new Drag({
22 | id: 'box-dragger2',
23 | canZoom: false,
24 | canRotate: false
25 | })
26 | new Drag({
27 | id: 'img-box',
28 | showAngle: true,
29 | showPosition: true
30 | })
31 | new Drag({
32 | id: 'test'
33 | })
34 | ```
35 | ## 具体实现(封装细节)
36 | ### 功能细节整理:
37 | - 旋转
38 | - 缩放
39 | - 平移
40 |
41 | ### 技术难点:
42 | - 旋转时要注意盒子每一个点的位置发生了变化
43 | - 针对拖拽后的盒子的left和top都有变化,计算其left和top时需将其按照中心轴旋转摆正,再进行计算
44 | - 当且仅有一个盒子是被选中的盒子,点击哪个选中哪个。(当前页面多个实例化Drag对象时,如何保证操作互不影响)
45 | - 实现的两种不同方式:
46 | - 可以选中某元素,直接给该元素内部加上操作的点
47 | - 有一个pannel,选中某元素时,将这个pannel定位到该元素的位置上
48 |
49 | 这两种方式都实现过一次,第一种比较简单,但是第一种,不好控制选中某个元素才让操作点展示。
50 |
51 | ### 如何封装:
52 | 考虑如何让用户快速上手使用,可参考的点:
53 | - 用户需要传入什么必须的参数
54 | - 暴露给用户什么可设置的参数和方法
55 |
56 | ### 实现过程
57 | #### 可配置参数
58 | 字段| 说明| 是否必填 | 默认值
59 | ---|---|---| ---
60 | id | 目标元素id | 是 | 无
61 | container| 父容器id | 否 | body
62 | canRotate | 是否可以旋转 | 否 | true
63 | canZoom | 是否可以缩放| 否 | true
64 | canPull | 是否可以拉升 | 否 | true
65 | canMove | 是否可以平移 | 否 | true
66 | showAngle | 展示角度 | 否 | false
67 | showPosition | 展示位置 | 否 | false
68 | isScale | 是否等比例缩放 | 否 | true
69 | showBorder | 是否展示pannel的border | 否 | false
70 |
71 | #### 属性
72 | - canRotate
73 | - canZoom
74 | - canPull
75 | - canMove
76 | - showAngle
77 | - isScale
78 | - id
79 | - container
80 | - targetObj
81 | - pannelDom 操作divdom
82 | - ...
83 |
84 | 具体看图:
85 | 
86 |
87 | #### 实现过程
88 | 1. 初始化参数
89 | 2. 初始化目标dom对象的位置:记录其:
90 | + left平距左
91 | + top
92 | + width
93 | + height
94 | + angle
95 | + rightBottomPoint 目标dom对象右下坐标
96 | + rightTopPoint 目标dom对象右上坐标
97 | + leftTopPoint 目标dom对象左上坐标
98 | + leftBottomPoint 目标dom对象左下坐标
99 | + leftMiddlePoint 目标dom对象左中坐标
100 | + rightMiddlePoint 目标dom对象右中坐标
101 | + topMiddlePoint 目标dom对象上中坐标
102 | + bottomMiddlePoint 目标dom对象下中坐标
103 | + centerPos 目标dom对象中心点坐标
104 | 3. 初始化pannel结构
105 | **当前的父容器中只有一个pannel结构**,每次实例化对象时,会判断一下如果当前这个父容器里已经存在id为pannel的结构,就将其子节点清空,按照当前实例化对象传进来的属性重新渲染pannel子结构。如果没有id为pannel的结构,就创建。
106 | 4. 初始化事件
107 | + 给pannelDom和targetObj绑定mousedown事件
108 | + 给document绑定mousemove和mouseup事件
109 |
110 | ```js
111 | initEvent () {
112 | document.addEventListener('mousemove', e => {
113 | e.preventDefault && e.preventDefault()
114 | this.moveChange(e, this.targetObj)
115 | })
116 | document.addEventListener('mouseup', e => {
117 | this.moveLeave(this.targetObj)
118 | })
119 | if (this.canMove) {
120 | // 外层给this.pannelDom添加mousedown事件,是在所有实例化结束后,panneldom被展示在最后一个实例化对象上,鼠标按下它时,触发moveInit事件
121 | this.pannelDom.onmousedown = e => {
122 | e.stopPropagation()
123 | this.moveInit(9, e, this.targetObj)
124 | }
125 | this.targetObj.onmousedown = e => {
126 | e.stopPropagation()
127 | this.moveInit(9, e, this.targetObj)
128 | this.initPannel()
129 | // 在点击其他未被选中元素时,pannel定位到该元素上,重写pannelDom事件,因为此时的this.pannelDom已经根据新的目标元素被重写
130 | this.pannelDom.onmousedown= e => {
131 | this.moveInit(9, e, this.targetObj)
132 | }
133 | }
134 | }
135 | }
136 | ```
137 | 5. dom操作
138 | + 旋转操作
139 | + 鼠标按下时,记录当前鼠标位置距离box中心位置的y/x的反正切函数A1。
140 |
141 | ```js
142 | this.mouseInit = {
143 | x: Math.floor(e.clientX),
144 | y: Math.floor(e.clientY)
145 | }
146 | this.preRadian = Math.atan2(this.mouseInit.y - this.centerPos.y, this.mouseInit.x - this.centerPos.x)
147 | ```
148 | + 鼠标移动时,记录再次计算鼠标位置距离box中心位置的y/x的反正切函数A2。
149 |
150 | ```js
151 | this.rotateCurrent = {
152 | x: Math.floor(e.clientX),
153 | y: Math.floor(e.clientY)
154 | }
155 | this.curRadian = Math.atan2(this.rotateCurrent.y - this.centerPos.y, this.rotateCurrent.x - this.centerPos.x)
156 | ```
157 | + 求A2-A1,求出移动的弧度
158 |
159 | ```js
160 | this.tranformRadian = this.curRadian - this.preRadian
161 | ```
162 | + 求出最后box的旋转角度,this.getRotate(target)是js中获取某dom元素的旋转角度的方法(粘贴过来的,亲测好使)
163 |
164 | ```js
165 | this.angle = this.getRotate(target) + Math.round(this.tranformRadian * 180 / Math.PI)
166 | this.preRadian = this.curRadian //鼠标移动的每一下都计算这个角度,所以每一下移动前的弧度值都上一次移动后的弧度值
167 | ```
168 | + 计算旋转后box每个点的坐标,根据余弦公式,传入:旋转前每点坐标,旋转中心坐标和旋转角度
169 |
170 | ```js
171 | let disAngle = this.angle - this.initAngle
172 | this.rightBottomPoint = this.getRotatedPoint(this.initRightBottomPoint, this.centerPos, disAngle)
173 | this.rightTopPoint = this.getRotatedPoint(this.initRightTopPoint, this.centerPos, disAngle)
174 | this.leftTopPoint = this.getRotatedPoint(this.initLeftTopPoint, this.centerPos, disAngle)
175 | this.leftBottomPoint = this.getRotatedPoint(this.initLeftBottomPoint, this.centerPos, disAngle)
176 | this.leftMiddlePoint = this.getRotatedPoint(this.initLeftMiddlePoint, this.centerPos, disAngle)
177 | this.rightMiddlePoint = this.getRotatedPoint(this.initRightMiddlePoint, this.centerPos, disAngle)
178 | this.topMiddlePoint = this.getRotatedPoint(this.initTopMiddlePoint, this.centerPos, disAngle)
179 | this.bottomMiddlePoint = this.getRotatedPoint(this.initBottomMiddlePoint, this.centerPos, disAngle)
180 | ```
181 | + 沿着一个方向拉升操作。
182 | + 沿着一个角缩放操作。
183 | 这两个操作,主要参考了一个大佬的拖拽思想实现的 [github wiki地址](https://github.com/shenhudong/snapping-demo/wiki)
184 |
185 | 6. 优化,mousemove事件添加节流函数
186 |
187 | ```js
188 | function throttle(fn, interval) {
189 | let canRun = true;
190 | return function () {
191 | if (!canRun) return;
192 | canRun = false;
193 | setTimeout(() => {
194 | fn.apply(this, arguments);
195 | canRun = true;
196 | }, interval);
197 | };
198 | }
199 | let that = this
200 | document.addEventListener('mousemove', throttle(function (e) {
201 | e.preventDefault && e.preventDefault()
202 | that.moveChange(e, that.targetObj)
203 | }, 10))
204 | ```
--------------------------------------------------------------------------------
/static/dragger.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: dragger
3 | * @Author: zy
4 | * @LastEditors: zy
5 | * @Date: 2019-04-04 16:35:59
6 | * @LastEditTime: 2019-04-18 15:01:43
7 | */
8 | /**
9 | * @description: 拖拽类
10 | * @param {
11 | * canRotate: true, 可旋转
12 | * canZoom: true, 可缩放
13 | * canPull: true, 可拉升
14 | * canMove: true, 可平移
15 | * showAngle: false, 展示角度
16 | * showPosition: false, 展示位置
17 | * isScale: true 是否等比例缩放
18 | * }
19 | * @return:
20 | */
21 | export default class Drag {
22 | constructor (obj) {
23 | this.id = obj.id
24 | this.initParameter(obj)
25 | }
26 | /**
27 | * @description: 初始化参数
28 | * @param {type}
29 | * @return:
30 | */
31 | initParameter (obj) {
32 | this.canRotate = obj.canRotate === undefined ? true : obj.canRotate
33 | this.canZoom = obj.canZoom === undefined ? true : obj.canZoom
34 | this.canPull = obj.canPull === undefined ? true : obj.canPull
35 | this.canMove = obj.canMove === undefined ? true : obj.canMove
36 | this.showBorder = obj.showBorder === undefined ? true : obj.showBorder
37 | this.showAngle = obj.showAngle
38 | this.showPosition = obj.showPosition
39 | this.isScale = obj.isScale === undefined ? true : obj.isScale
40 | this.container = obj.container ? document.getElementById(obj.container) : document.body
41 | this.targetObj = document.getElementById(obj.id)
42 | this.initPos()
43 | this.initPannel()
44 | this.initEvent()
45 | }
46 | /**
47 | * @description: 判断是否有pannel面板
48 | * @param {type}
49 | * @return:
50 | */
51 | initPannel () {
52 | this.pannelDom = document.querySelector('#pannel')
53 | if (!this.pannelDom) {
54 | this.pannelDom = document.createElement('div')
55 | this.pannelDom.id = 'pannel'
56 | this.container.appendChild(this.pannelDom)
57 | } else {
58 | this.pannelDom.innerHTML = ''
59 | }
60 | this.initPannelDom()
61 | }
62 | /**
63 | * @description: 初始化事件
64 | * @param {type}
65 | * @return:
66 | */
67 | initEvent () {
68 | function throttle(fn, interval) {
69 | let canRun = true;
70 | return function () {
71 | if (!canRun) return;
72 | canRun = false;
73 | setTimeout(() => {
74 | fn.apply(this, arguments);
75 | canRun = true;
76 | }, interval);
77 | };
78 | }
79 | let that = this
80 | document.addEventListener('mousemove', throttle(function (e) {
81 | e.preventDefault && e.preventDefault()
82 | that.moveChange(e, that.targetObj)
83 | }, 10))
84 | document.addEventListener('mouseup', e => {
85 | this.moveLeave(this.targetObj)
86 | })
87 | // this.container.addEventListener('mousedown', e => {
88 | // this.mergeBox.flag = true
89 | // this.initPannel()
90 | // this.mergeBox.initPos = {
91 | // x: Math.floor(e.clientX),
92 | // y: Math.floor(e.clientY)
93 | // }
94 | // this.container.addEventListener('mousemove', e => {
95 | // if (this.mergeBox.flag) {
96 | // this.mergeBox.left = this.mergeBox.initPos.x
97 | // this.mergeBox.top = this.mergeBox.initPos.y
98 | // this.pannelDom.style.left = `${this.mergeBox.initPos.x}px`
99 | // this.pannelDom.style.top = `${this.mergeBox.initPos.y}px`
100 | // this.pannelDom.style.width = `${Math.floor(e.clientX) - this.mergeBox.initPos.x}px`
101 | // this.pannelDom.style.height = `${Math.floor(e.clientY) - this.mergeBox.initPos.y}px`
102 | // }
103 | // })
104 | // })
105 | if (this.canMove) {
106 | // 外层给this.pannelDom添加mousedown事件,是在所有实例化结束后,panneldom被展示在最后一个实例化对象上,鼠标按下它时,触发moveInit事件
107 | this.pannelDom.onmousedown = e => {
108 | e.stopPropagation()
109 | this.moveInit(9, e, this.targetObj)
110 | }
111 | this.targetObj.onmousedown = e => {
112 | e.stopPropagation()
113 | this.moveInit(9, e, this.targetObj)
114 | this.initPannel()
115 | // 在点击其他未被选中元素时,pannel定位到该元素上,重写pannelDom事件,因为此时的this.pannelDom已经根据新的目标元素被重写
116 | this.pannelDom.onmousedown= e => {
117 | this.moveInit(9, e, this.targetObj)
118 | }
119 | }
120 | }
121 | }
122 | /**
123 | * @description: 初始化Pannel内部dom结构
124 | * @param {}
125 | * @return: null
126 | */
127 | initPannelDom () {
128 | this.pannelDom.style.left = `${this.left}px`
129 | this.pannelDom.style.top = `${this.top}px`
130 | this.pannelDom.style.width = `${this.width}px`
131 | this.pannelDom.style.height = `${this.height}px`
132 | this.pannelDom.style.transform = `rotate(${this.angle}deg)`
133 | if (this.canRotate) {
134 | let rotatePoint = document.createElement('span')
135 | rotatePoint.className = `${this.id}-dragger-rotate-point dragger-rotate-point`
136 | if (this.showAngle) {
137 | this.angleDom = document.createElement('span')
138 | this.angleDom.className = `dragger-rotate-angle ${this.id}-dragger-rotate-angle`
139 | this.angleDom.style.display = 'none'
140 | this.pannelDom.appendChild(this.angleDom)
141 | }
142 | this.pannelDom.appendChild(rotatePoint)
143 | rotatePoint.addEventListener('mousedown', e => {
144 | e.stopPropagation()
145 | this.moveInit(0, e, this.targetObj)
146 | })
147 | }
148 | if (this.showBorder) {
149 | for (let i = 1; i < 5; i++) {
150 | let baseLine = document.createElement('span')
151 | baseLine.className = `${this.id}-dragger-base-line dragger-base-line dragger-line${i}`
152 | this.pannelDom.appendChild(baseLine)
153 | }
154 | }
155 | if (this.canZoom) {
156 | for (let i = 1; i < 5; i++) {
157 | let zoomPoint = document.createElement('span')
158 | zoomPoint.className = `${this.id}-dragger-base-piont dragger-base-piont dragger-zoom${i}`
159 | this.pannelDom.appendChild(zoomPoint)
160 | zoomPoint.addEventListener('mousedown', e => {
161 | e.stopPropagation()
162 | this.moveInit(i, e, this.targetObj)
163 | })
164 | }
165 | }
166 | if (this.canPull) {
167 | for (let i = 5; i < 9; i++) {
168 | let pullPoint = document.createElement('span')
169 | pullPoint.className = `${this.id}-dragger-base-piont dragger-base-piont dragger-pull${i}`
170 | this.pannelDom.appendChild(pullPoint)
171 | pullPoint.addEventListener('mousedown', e => {
172 | e.stopPropagation()
173 | this.moveInit(i, e, this.targetObj)
174 | })
175 | }
176 | }
177 | if (this.canMove && this.showPosition) {
178 | this.positionDom = document.createElement('span')
179 | this.positionDom.className = 'dragger-position'
180 | this.pannelDom.appendChild(this.positionDom)
181 | this.positionDom.style.display = 'none'
182 | }
183 | }
184 | /**
185 | * @description: 初始化targetObj的位置
186 | * @param {type}
187 | * @return:
188 | */
189 | initPos () {
190 | this.left = this.targetObj.offsetLeft
191 | this.top = this.targetObj.offsetTop
192 | this.width = this.targetObj.offsetWidth
193 | this.height = this.targetObj.offsetHeight
194 | this.angle = this.getRotate(this.targetObj)
195 | // 记录初始盒子位置右下
196 | this.rightBottomPoint = {
197 | x: this.width + this.left,
198 | y: this.height + this.top
199 | }
200 | // 记录初始盒子右上角位置
201 | this.rightTopPoint = {
202 | x: this.width + this.left,
203 | y: this.top
204 | }
205 | // 记录左上角的位置
206 | this.leftTopPoint = {
207 | x: this.left,
208 | y: this.top
209 | }
210 | // 左下
211 | this.leftBottomPoint = {
212 | x: this.left,
213 | y: this.top + this.height
214 | }
215 | // 左中
216 | this.leftMiddlePoint = {
217 | x: this.left,
218 | y: this.top + this.height / 2
219 | }
220 | // 右中
221 | this.rightMiddlePoint = {
222 | x: this.left + this.width,
223 | y: this.top + this.height / 2
224 | }
225 | // 上中
226 | this.topMiddlePoint = {
227 | x: this.left + this.width / 2,
228 | y: this.top
229 | }
230 | // 下中
231 | this.bottomMiddlePoint= {
232 | x: this.left + this.width / 2,
233 | y: this.top + this.height
234 | }
235 | // 记录中心位置
236 | this.centerPos = {
237 | x: this.left + this.width / 2,
238 | y: this.top + this.height / 2
239 | }
240 | }
241 | moveInit (type, e, target) {
242 | this.type = Number(type)
243 | this.mouseInit = {
244 | x: Math.floor(e.clientX),
245 | y: Math.floor(e.clientY)
246 | }
247 | this.scale = target.offsetWidth / target.offsetHeight
248 | this.initAngle = this.angle
249 | this.initRightBottomPoint = this.rightBottomPoint
250 | this.initRightTopPoint = this.rightTopPoint
251 | this.initLeftTopPoint = this.leftTopPoint
252 | this.initLeftBottomPoint = this.leftBottomPoint
253 | this.initLeftMiddlePoint = this.leftMiddlePoint
254 | this.initRightMiddlePoint = this.rightMiddlePoint
255 | this.initTopMiddlePoint = this.topMiddlePoint
256 | this.initBottomMiddlePoint = this.bottomMiddlePoint
257 | this.initCenterPos = this.centerPos
258 | this.initPosition = {
259 | x: this.left,
260 | y: this.top
261 | }
262 | if (type === 0) {
263 | this.rotateFlag = true
264 | this.preRadian = Math.atan2(this.mouseInit.y - this.centerPos.y, this.mouseInit.x - this.centerPos.x)
265 | } else if (type < 10) {
266 | this.canChange = true
267 | }
268 | }
269 | moveChange (e, target) {
270 | let newHeight, newWidth, newRightBottomPoint, newLeftTopPoint, newLeftBottomPoint, newRightTopPoint, rotateCurrentPos
271 | switch (this.type) {
272 | case 0:
273 | if (this.rotateFlag) {
274 | this.rotateCurrent = {
275 | x: Math.floor(e.clientX),
276 | y: Math.floor(e.clientY)
277 | }
278 | this.curRadian = Math.atan2(this.rotateCurrent.y - this.centerPos.y, this.rotateCurrent.x - this.centerPos.x)
279 | this.tranformRadian = this.curRadian - this.preRadian
280 | this.angle = this.getRotate(target) + Math.round(this.tranformRadian * 180 / Math.PI)
281 | target.style.transform = `rotate(${this.angle}deg)`
282 | this.pannelDom.style.transform = `rotate(${this.angle}deg)`
283 | if (this.showAngle) {
284 | this.angleDom.innerHTML = this.angle
285 | this.angleDom.style.display = 'block'
286 | }
287 | this.preRadian = this.curRadian
288 | // 重新计算旋转后四个点的坐标变化
289 | let disAngle = this.angle - this.initAngle
290 | this.rightBottomPoint = this.getRotatedPoint(this.initRightBottomPoint, this.centerPos, disAngle)
291 | this.rightTopPoint = this.getRotatedPoint(this.initRightTopPoint, this.centerPos, disAngle)
292 | this.leftTopPoint = this.getRotatedPoint(this.initLeftTopPoint, this.centerPos, disAngle)
293 | this.leftBottomPoint = this.getRotatedPoint(this.initLeftBottomPoint, this.centerPos, disAngle)
294 | this.leftMiddlePoint = this.getRotatedPoint(this.initLeftMiddlePoint, this.centerPos, disAngle)
295 | this.rightMiddlePoint = this.getRotatedPoint(this.initRightMiddlePoint, this.centerPos, disAngle)
296 | this.topMiddlePoint = this.getRotatedPoint(this.initTopMiddlePoint, this.centerPos, disAngle)
297 | this.bottomMiddlePoint = this.getRotatedPoint(this.initBottomMiddlePoint, this.centerPos, disAngle)
298 | }
299 | case 1:
300 | if (this.canChange) {
301 | this.centerPos = {
302 | x: Math.floor((e.clientX + this.rightBottomPoint.x) / 2),
303 | y: Math.floor((e.clientY + this.rightMiddlePoint.y) / 2)
304 | }
305 | // 计算旋转为水平角度的两点坐标
306 | newLeftTopPoint = this.getRotatedPoint({
307 | x: e.clientX,
308 | y: e.clientY
309 | }, this.centerPos, -this.initAngle)
310 | newRightBottomPoint = this.getRotatedPoint(this.rightBottomPoint, this.centerPos, -this.initAngle)
311 | newWidth = newRightBottomPoint.x - newLeftTopPoint.x
312 | newHeight = newRightBottomPoint.y - newLeftTopPoint.y
313 | if (this.isScale) {
314 | if (newWidth / newHeight > this.scale) {
315 | newLeftTopPoint.x = newLeftTopPoint.x + Math.abs(newWidth - newHeight * this.scale)
316 | newWidth = newHeight * this.scale
317 | } else {
318 | newLeftTopPoint.y = newLeftTopPoint.y + Math.abs(newHeight - newWidth / this.scale)
319 | newHeight = newWidth / this.scale
320 | }
321 | // 计算出左上角等比角度变换后水平坐标后,再计算旋转后的角度
322 | var rotateLeftTopPoint = this.getRotatedPoint(newLeftTopPoint, this.centerPos, this.initAngle)
323 | this.centerPos = {
324 | x: Math.floor((rotateLeftTopPoint.x + this.rightBottomPoint.x) / 2),
325 | y: Math.floor((rotateLeftTopPoint.y + this.rightBottomPoint.y) / 2)
326 | }
327 | newLeftTopPoint = this.getRotatedPoint(rotateLeftTopPoint, this.centerPos, -this.initAngle)
328 | newRightBottomPoint = this.getRotatedPoint(this.rightBottomPoint, this.centerPos, -this.initAngle)
329 | newWidth = newRightBottomPoint.x - newLeftTopPoint.x
330 | newHeight = newRightBottomPoint.y - newLeftTopPoint.y
331 | }
332 | if (newWidth <= 12) {
333 | newWidth = 12
334 | newHeight = Math.floor(newWidth / this.scale)
335 | newLeftTopPoint.x = newRightBottomPoint.x - newWidth
336 | newLeftTopPoint.y = newRightBottomPoint.y - newHeight
337 | }
338 | if (newHeight <= 12) {
339 | newHeight = 12
340 | newWidth = Math.floor(newHeight * this.scale)
341 | newLeftTopPoint.y = newRightBottomPoint.y - newHeight
342 | newLeftTopPoint.x = newRightBottomPoint.x - newWidth
343 | }
344 | if (newHeight > 12 && newWidth > 12) {
345 | this.left = newLeftTopPoint.x
346 | this.top = newLeftTopPoint.y
347 | this.width = newWidth
348 | this.height = newHeight
349 | target.style.left = `${this.left}px`
350 | target.style.top = `${this.top}px`
351 | target.style.width = `${this.width}px`
352 | target.style.height = `${this.height}px`
353 | this.pannelDom.style.left = `${this.left}px`
354 | this.pannelDom.style.top = `${this.top}px`
355 | this.pannelDom.style.width = `${this.width}px`
356 | this.pannelDom.style.height = `${this.height}px`
357 | }
358 | }
359 | break;
360 | case 2:
361 | if (this.canChange) {
362 | this.centerPos = {
363 | x: Math.floor((e.clientX + this.rightTopPoint.x) / 2),
364 | y: Math.floor((e.clientY + this.rightTopPoint.y) / 2)
365 | }
366 | newLeftBottomPoint = this.getRotatedPoint({
367 | x: e.clientX,
368 | y: e.clientY
369 | }, this.centerPos, -this.initAngle)
370 | newRightTopPoint = this.getRotatedPoint(this.rightTopPoint, this.centerPos, -this.initAngle)
371 | newWidth = newRightTopPoint.x - newLeftBottomPoint.x
372 | newHeight = newLeftBottomPoint.y - newRightTopPoint.y
373 | if (this.isScale) {
374 | if (newWidth / newHeight > this.scale) {
375 | newLeftBottomPoint.x = newLeftBottomPoint.x + Math.abs(newWidth - newHeight * this.scale)
376 | newWidth = newHeight * this.scale
377 | } else {
378 | newLeftBottomPoint.y = newLeftBottomPoint.y - Math.abs(newHeight - newWidth / this.scale)
379 | newHeight = newWidth / this.scale
380 | }
381 | var rotatedLeftBottomPoint = this.getRotatedPoint(newLeftBottomPoint, this.centerPos, this.initAngle)
382 | this.centerPos = {
383 | x: Math.floor((rotatedLeftBottomPoint.x + this.rightTopPoint.x) / 2),
384 | y: Math.floor((rotatedLeftBottomPoint.y + this.rightTopPoint.y) / 2)
385 | }
386 | newLeftBottomPoint = this.getRotatedPoint(rotatedLeftBottomPoint, this.centerPos, -this.initAngle)
387 | newRightTopPoint = this.getRotatedPoint(this.rightTopPoint, this.centerPos, -this.initAngle)
388 | newWidth = newRightTopPoint.x - newLeftBottomPoint.x
389 | newHeight = newLeftBottomPoint.y - newRightTopPoint.y
390 | }
391 | if (newHeight <= 12) {
392 | newHeight = 12
393 | newWidth = Math.floor(newHeight * this.scale)
394 | newLeftBottomPoint = {
395 | x: newRightTopPoint.x - newWidth,
396 | y: newRightTopPoint.y + newHeight
397 | }
398 | }
399 | if (newWidth <= 12) {
400 | newWidth = 12
401 | newHeight = Math.floor(newWidth / this.scale)
402 | newLeftBottomPoint = {
403 | x: newRightTopPoint.x - newWidth,
404 | y: newRightTopPoint.y + newHeight
405 | }
406 | }
407 | if (newHeight > 12 && newHeight > 12) {
408 | this.left = newLeftBottomPoint.x
409 | this.top = newRightTopPoint.y
410 | this.width = newWidth
411 | this.height = newHeight
412 | target.style.left = `${this.left}px`
413 | target.style.top = `${this.top}px`
414 | target.style.width = `${this.width}px`
415 | target.style.height = `${this.height}px`
416 | this.pannelDom.style.left = `${this.left}px`
417 | this.pannelDom.style.top = `${this.top}px`
418 | this.pannelDom.style.width = `${this.width}px`
419 | this.pannelDom.style.height = `${this.height}px`
420 | }
421 | }
422 | break;
423 | case 3:
424 | if (this.canChange) {
425 | this.centerPos = {
426 | x: Math.floor((e.clientX + this.leftBottomPoint.x) / 2),
427 | y: Math.floor((e.clientY + this.leftBottomPoint.y) / 2)
428 | }
429 | newRightTopPoint = this.getRotatedPoint({
430 | x: e.clientX,
431 | y: e.clientY
432 | }, this.centerPos, -this.initAngle)
433 | newLeftBottomPoint = this.getRotatedPoint(this.leftBottomPoint, this.centerPos, -this.initAngle)
434 | newWidth = newRightTopPoint.x - newLeftBottomPoint.x
435 | newHeight = newLeftBottomPoint.y - newRightTopPoint.y
436 | if (this.isScale) {
437 | if (newWidth / newHeight > this.scale) {
438 | newRightTopPoint.x = newRightTopPoint.x - Math.abs(newWidth - newHeight * this.scale)
439 | newWidth = newHeight * this.scale
440 | } else {
441 | newRightTopPoint.y = newRightTopPoint.y + Math.abs(newHeight - newWidth / this.scale)
442 | newHeight = newWidth / this.scale
443 | }
444 | let rotatedRightTopPoint = this.getRotatedPoint(newRightTopPoint, this.centerPos, this.initAngle)
445 | this.centerPos = {
446 | x: Math.floor((rotatedRightTopPoint.x + this.leftBottomPoint.x) / 2),
447 | y: Math.floor((rotatedRightTopPoint.y + this.leftBottomPoint.y) / 2)
448 | }
449 | newLeftBottomPoint = this.getRotatedPoint(this.leftBottomPoint, this.centerPos, -this.initAngle)
450 | newRightTopPoint = this.getRotatedPoint(rotatedRightTopPoint, this.centerPos, -this.initAngle)
451 | newWidth = newRightTopPoint.x - newLeftBottomPoint.x
452 | newHeight = newLeftBottomPoint.y - newRightTopPoint.y
453 | }
454 | if (newWidth <= 12) {
455 | newWidth = 12
456 | newHeight = Math.floor(newWidth / this.scale)
457 | newRightTopPoint = {
458 | x: newLeftBottomPoint.x + newWidth,
459 | y: newLeftBottomPoint.y - newHeight
460 | }
461 | }
462 | if (newHeight <= 12) {
463 | newHeight = 12
464 | newWidth = Math.floor(newHeight * this.scale)
465 | newRightTopPoint = {
466 | x: newLeftBottomPoint.x + newWidth,
467 | y: newLeftBottomPoint.y - newHeight
468 | }
469 | }
470 | if (newWidth > 12 && newHeight > 12) {
471 | this.left = newLeftBottomPoint.x
472 | this.top = newRightTopPoint.y
473 | this.width = newWidth
474 | this.height = newHeight
475 | target.style.left = `${this.left}px`
476 | target.style.top = `${this.top}px`
477 | target.style.width = `${this.width}px`
478 | target.style.height = `${this.height}px`
479 | this.pannelDom.style.left = `${this.left}px`
480 | this.pannelDom.style.top = `${this.top}px`
481 | this.pannelDom.style.width = `${this.width}px`
482 | this.pannelDom.style.height = `${this.height}px`
483 | }
484 | }
485 | break;
486 | case 4:
487 | if (this.canChange) {
488 | this.centerPos = {
489 | x: Math.floor((e.clientX + this.leftTopPoint.x) / 2),
490 | y: Math.floor((e.clientY + this.leftTopPoint.y) / 2)
491 | }
492 | newRightBottomPoint = this.getRotatedPoint({
493 | x: e.clientX,
494 | y: e.clientY
495 | }, this.centerPos, -this.initAngle)
496 | newLeftTopPoint = this.getRotatedPoint(this.leftTopPoint, this.centerPos, -this.initAngle)
497 | newWidth = newRightBottomPoint.x - newLeftTopPoint.x
498 | newHeight = newRightBottomPoint.y - newLeftTopPoint.y
499 | if (this.isScale) {
500 | if (newWidth / newHeight > this.scale) {
501 | newRightBottomPoint.x = newRightBottomPoint.x - Math.abs(newWidth - newHeight * this.scale)
502 | newWidth = newHeight * this.scale
503 | } else {
504 | newRightBottomPoint.y = newRightBottomPoint.y - Math.abs(newHeight - newWidth / this.scale)
505 | newHeight = newWidth / this.scale
506 | }
507 | let rotatedRightBottomPoint = this.getRotatedPoint(newRightBottomPoint, this.centerPos, this.initAngle)
508 | this.centerPos = {
509 | x: Math.floor((rotatedRightBottomPoint.x + this.leftTopPoint.x) / 2),
510 | y: Math.floor((rotatedRightBottomPoint.y + this.leftTopPoint.y) / 2)
511 | }
512 | newLeftTopPoint = this.getRotatedPoint(this.leftTopPoint, this.centerPos, -this.initAngle)
513 | newRightBottomPoint = this.getRotatedPoint(rotatedRightBottomPoint, this.centerPos, -this.initAngle)
514 | newWidth = newRightBottomPoint.x - newLeftTopPoint.x
515 | newHeight = newRightBottomPoint.y - newLeftTopPoint.y
516 | }
517 | if (newWidth <= 12) {
518 | newWidth = 12
519 | newHeight = Math.floor(newWidth / this.scale)
520 | newRightBottomPoint = {
521 | x: newLeftTopPoint.x + newWidth,
522 | y: newLeftTopPoint.y + newHeight
523 | }
524 | }
525 | if (newHeight <= 12) {
526 | newHeight = 12
527 | newWidth = Math.floor(newHeight * this.scale)
528 | newRightBottomPoint = {
529 | x: newLeftTopPoint.x + newWidth,
530 | y: newLeftTopPoint.y + newHeight
531 | }
532 | }
533 | if (newWidth > 12 && newHeight > 12) {
534 | this.left = newLeftTopPoint.x
535 | this.top = newLeftTopPoint.y
536 | this.width = newWidth
537 | this.height = newHeight
538 | target.style.left = `${this.left}px`
539 | target.style.top = `${this.top}px`
540 | target.style.width = `${this.width}px`
541 | target.style.height = `${this.height}px`
542 | this.pannelDom.style.left = `${this.left}px`
543 | this.pannelDom.style.top = `${this.top}px`
544 | this.pannelDom.style.width = `${this.width}px`
545 | this.pannelDom.style.height = `${this.height}px`
546 | }
547 | }
548 | break;
549 | case 5:
550 | if (this.canChange) {
551 | // 计算出鼠标现在所在的点,经过以bottommiddle点反向旋转后的位置,从而得到其y轴坐标点与topmiddle的x轴坐标结合,求出旋转后图形的topmiddle
552 | rotateCurrentPos = this.getRotatedPoint({
553 | x: e.clientX,
554 | y: e.clientY
555 | }, this.bottomMiddlePoint, -this.initAngle)
556 | let rotatedTopMiddlePoint = {
557 | x: this.bottomMiddlePoint.x,
558 | y: rotateCurrentPos.y
559 | }
560 | newHeight = this.bottomMiddlePoint.y - rotatedTopMiddlePoint.y
561 |
562 | // newHeight = Math.sqrt(Math.pow((this.topMiddlePoint.x - this.bottomMiddlePoint.x), 2) + Math.pow((this.topMiddlePoint.y - this.bottomMiddlePoint.y), 2))
563 | if (newHeight <= 12) {
564 | newHeight = 12
565 | rotatedTopMiddlePoint.y = this.bottomMiddlePoint.y - 12
566 | }
567 | // 计算转回去的topmiddle点坐标
568 | this.topMiddlePoint = this.getRotatedPoint(rotatedTopMiddlePoint, this.bottomMiddlePoint, this.initAngle)
569 | this.centerPos = {
570 | x: (this.topMiddlePoint.x + this.bottomMiddlePoint.x) / 2,
571 | y: (this.topMiddlePoint.y + this.bottomMiddlePoint.y) / 2
572 | }
573 | this.left = this.centerPos.x - target.offsetWidth / 2
574 | this.top = this.centerPos.y - newHeight / 2
575 | this.height = newHeight
576 | target.style.left = `${this.left}px`
577 | target.style.height = `${this.height}px`
578 | target.style.top = `${this.top}px`
579 | this.pannelDom.style.left = `${this.left}px`
580 | this.pannelDom.style.height = `${this.height}px`
581 | this.pannelDom.style.top = `${this.top}px`
582 | }
583 | break;
584 | case 6:
585 | if (this.canChange) {
586 | rotateCurrentPos = this.getRotatedPoint({
587 | x: e.clientX,
588 | y: e.clientY
589 | }, this.rightMiddlePoint, -this.initAngle)
590 | let rotatedLeftMiddlePonit = {
591 | x: rotateCurrentPos.x,
592 | y: this.rightMiddlePoint.y
593 | }
594 | newWidth = this.rightMiddlePoint.x - rotatedLeftMiddlePonit.x
595 | if (newWidth <= 12) {
596 | newWidth = 12
597 | rotatedLeftMiddlePonit.x = this.rightMiddlePoint.x - 12
598 | }
599 | this.leftMiddlePoint = this.getRotatedPoint(rotatedLeftMiddlePonit, this.rightMiddlePoint, this.initAngle)
600 | this.centerPos = {
601 | x: Math.floor((this.leftMiddlePoint.x + this.rightMiddlePoint.x) / 2),
602 | y: Math.floor((this.leftMiddlePoint.y + this.rightMiddlePoint.y) / 2)
603 | }
604 | this.left = this.centerPos.x - newWidth / 2
605 | this.top = this.centerPos.y - target.offsetHeight / 2
606 | this.width = newWidth
607 | target.style.left = `${this.left}px`
608 | target.style.top = `${this.top}px`
609 | target.style.width = `${this.width}px`
610 | this.pannelDom.style.left = `${this.left}px`
611 | this.pannelDom.style.top = `${this.top}px`
612 | this.pannelDom.style.width = `${this.width}px`
613 | }
614 | break;
615 | case 7:
616 | if (this.canChange) {
617 | rotateCurrentPos = this.getRotatedPoint({
618 | x: e.clientX,
619 | y: e.clientY
620 | }, this.topMiddlePoint, -this.initAngle)
621 | let rotatedBottomMiddlePoint = {
622 | x: this.topMiddlePoint.x,
623 | y: rotateCurrentPos.y
624 | }
625 | newHeight = rotatedBottomMiddlePoint.y - this.topMiddlePoint.y
626 | if (newHeight <= 12) {
627 | newHeight = 12
628 | rotatedBottomMiddlePoint.y = this.topMiddlePoint.y - 12
629 | }
630 | this.bottomMiddlePoint = this.getRotatedPoint(rotatedBottomMiddlePoint, this.topMiddlePoint, this.initAngle)
631 | this.centerPos = {
632 | x: Math.floor((this.bottomMiddlePoint.x + this.topMiddlePoint.x) / 2),
633 | y: Math.floor((this.bottomMiddlePoint.y + this.topMiddlePoint.y) / 2)
634 | }
635 | this.left = this.centerPos.x - target.offsetWidth / 2
636 | this.top = this.centerPos.y - newHeight / 2
637 | this.height = newHeight
638 | target.style.left = `${this.left}px`
639 | target.style.top = `${this.top}px`
640 | target.style.height = `${this.height}px`
641 | this.pannelDom.style.left = `${this.left}px`
642 | this.pannelDom.style.top = `${this.top}px`
643 | this.pannelDom.style.height = `${this.height}px`
644 | }
645 | break;
646 | case 8:
647 | if (this.canChange) {
648 | rotateCurrentPos = this.getRotatedPoint({
649 | x: e.clientX,
650 | y: e.clientY
651 | }, this.leftMiddlePoint, -this.initAngle)
652 | let rotatedRightMiddlePoint = {
653 | x: rotateCurrentPos.x,
654 | y: this.leftMiddlePoint.y
655 | }
656 | newWidth = rotatedRightMiddlePoint.x - this.leftMiddlePoint.x
657 | if (newWidth <= 12) {
658 | newWidth = 12
659 | rotatedRightMiddlePoint.x = this.leftMiddlePoint.x + 12
660 | }
661 | this.rightMiddlePoint = this.getRotatedPoint(rotatedRightMiddlePoint, this.leftMiddlePoint, this.initAngle)
662 | this.centerPos = {
663 | x: Math.floor((this.leftMiddlePoint.x + this.rightMiddlePoint.x) / 2),
664 | y: Math.floor((this.leftMiddlePoint.y + this.rightMiddlePoint.y) / 2)
665 | }
666 | this.left = this.centerPos.x - newWidth / 2
667 | this.top = this.centerPos.y - target.offsetHeight / 2
668 | this.width = newWidth
669 | target.style.left = `${this.left}px`
670 | target.style.top = `${this.top}px`
671 | target.style.width = `${newWidth}px`
672 | this.pannelDom.style.left = `${this.left}px`
673 | this.pannelDom.style.top = `${this.top}px`
674 | this.pannelDom.style.width = `${this.width}px`
675 | }
676 | break;
677 | case 9:
678 | if (this.canChange) {
679 | let dis = {
680 | x: Math.floor(e.clientX - this.mouseInit.x),
681 | y: Math.floor(e.clientY - this.mouseInit.y)
682 | }
683 | this.left = this.initPosition.x + dis.x
684 | this.top = this.initPosition.y + dis.y
685 | target.style.left = `${this.left}px`
686 | target.style.top = `${this.top}px`
687 | this.pannelDom.style.left = `${this.left}px`
688 | this.pannelDom.style.top = `${this.top}px`
689 | this.pannelDom.style.width = `${this.width}px`
690 | this.pannelDom.style.height = `${this.height}px`
691 | this.centerPos = {
692 | x: this.initCenterPos.x + dis.x,
693 | y: this.initCenterPos.y + dis.y
694 | }
695 | if (this.showPosition) {
696 | this.positionDom.style.display = 'inline-block'
697 | this.positionDom.innerHTML = `X: ${this.left} Y: ${this.top}`
698 | }
699 | }
700 | break;
701 | }
702 | }
703 | moveLeave () {
704 | if (this.canChange || this.rotateFlag) {
705 | this.rotateFlag = false
706 | this.canChange = false
707 | if (this.angleDom) this.angleDom.style.display = 'none'
708 | if (this.positionDom) this.positionDom.style.display = 'none'
709 | this.getTransferPosition(this.left, this.top, this.width, this.height, this.angle, this.centerPos)
710 | }
711 | }
712 | getRotate (target) {
713 | var st = window.getComputedStyle(target, null);
714 | var tr = st.getPropertyValue("-webkit-transform") ||
715 | st.getPropertyValue("-moz-transform") ||
716 | st.getPropertyValue("-ms-transform") ||
717 | st.getPropertyValue("-o-transform") ||
718 | st.getPropertyValue("transform") || "FAIL";
719 | // With rotate(30deg)...
720 | // matrix(0.866025, 0.5, -0.5, 0.866025, 0px, 0px)
721 | if (tr !== 'none') {
722 | var values = tr.split('(')[1].split(')')[0].split(',');
723 | var a = values[0];
724 | var b = values[1];
725 | var c = values[2];
726 | var d = values[3];
727 | var scale = Math.sqrt(a * a + b * b);
728 | // arc sin, convert from radians to degrees, round
729 | var sin = b / scale;
730 | // next line works for 30deg but not 130deg (returns 50);
731 | // var angle = Math.round(Math.asin(sin) * (180/Math.PI));
732 | var angle = Math.round(Math.atan2(b, a) * (180 / Math.PI))
733 | if (angle < 0) {
734 | angle = 360 + angle
735 | }
736 | return angle
737 | } else {
738 | return 0
739 | }
740 | }
741 | getRotatedPoint (curPos, centerPos, angle) {
742 | return {
743 | x: Math.floor((curPos.x - centerPos.x) * Math.cos(Math.PI / 180 * angle) - (curPos.y - centerPos.y) * Math.sin(Math.PI / 180 * angle) + centerPos.x),
744 | y: Math.floor((curPos.x - centerPos.x) * Math.sin(Math.PI / 180 * angle) + (curPos.y - centerPos.y) * Math.cos(Math.PI / 180 * angle) + centerPos.y)
745 | }
746 | }
747 | getTransferPosition (left, top, width, height, angle, center) {
748 | // 计算变换后的方框四个角的位置
749 | var a1 = {
750 | x: left,
751 | y: top
752 | }
753 | var a2 = {
754 | x: left,
755 | y: top + height
756 | }
757 | var a3 = {
758 | x: left + width,
759 | y: top
760 | }
761 | var a4 = {
762 | x: left + width,
763 | y: top + height
764 | }
765 | var a5 = {
766 | x: left,
767 | y: top + height / 2
768 | }
769 | var a6 = {
770 | x: left + width,
771 | y: top + height / 2
772 | }
773 | var a7 = {
774 | x: left + width / 2,
775 | y: top
776 | }
777 | var a8 = {
778 | x: left + width / 2,
779 | y: top + height
780 | }
781 | this.leftTopPoint = this.getRotatedPoint(a1, center, angle)
782 | this.leftBottomPoint = this.getRotatedPoint(a2, center, angle)
783 | this.rightTopPoint = this.getRotatedPoint(a3, center, angle)
784 | this.rightBottomPoint = this.getRotatedPoint(a4, center, angle)
785 | this.leftMiddlePoint = this.getRotatedPoint(a5, center, angle)
786 | this.rightMiddlePoint = this.getRotatedPoint(a6, center, angle)
787 | this.topMiddlePoint = this.getRotatedPoint(a7, center, angle)
788 | this.bottomMiddlePoint = this.getRotatedPoint(a8, center, angle)
789 | }
790 | }
791 |
--------------------------------------------------------------------------------