├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── LICENSE ├── README.md ├── build ├── build.js ├── check-versions.js ├── dev-client.js ├── dev-server.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js ├── webpack.prod.conf.js └── webpack.test.conf.js ├── config ├── dev.env.js ├── index.js ├── prod.env.js └── test.env.js ├── index.html ├── package-lock.json ├── package.json ├── src ├── App.vue ├── assets │ └── logo.png ├── components │ ├── Canvas.vue │ └── sign.vue ├── main.js ├── router │ └── index.js └── utils │ ├── draw.js │ └── iload.js ├── static └── .gitkeep └── yarn.lock /.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-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["istanbul"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | node: true, 10 | commonjs: true 11 | }, 12 | extends: 'airbnb-base', 13 | // required to lint *.vue files 14 | plugins: [ 15 | 'html' 16 | ], 17 | // check if imports actually resolve 18 | 'settings': { 19 | 'import/resolver': { 20 | 'webpack': { 21 | 'config': 'build/webpack.base.conf.js' 22 | } 23 | } 24 | }, 25 | // add your custom rules here 26 | 'rules': { 27 | // don't require .vue extension when importing 28 | 'import/extensions': ['error', 'always', { 29 | 'js': 'never', 30 | 'vue': 'never' 31 | }], 32 | // allow debugger during development 33 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 34 | 'no-console': 0, 35 | 'no-plusplus': 0, 36 | 'no-continue': 0, 37 | 'no-underscore-dangle': 0, 38 | 'no-param-reassign': 0, 39 | 'no-restricted-syntax': 0, 40 | 'consistent-return': 0, 41 | 'no-bitwise': 0, 42 | 'import/no-unresolved': 0, 43 | 'no-trailing-spaces': 0, 44 | 'no-fallthrough': 0 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.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 | .node-version 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 | // to edit target browsers: use "browserlist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Louis 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 | # canvas-draw 2 | 3 | ![License MIT](https://img.shields.io/npm/l/express.svg) 4 | 5 | [demo](http://louiszhai.github.io/res/canvasDraw/) 微信中签名并分享请戳 [sign demo](http://louiszhai.github.io/res/canvasDraw/#/sign) 6 | 7 | > use canvas to draw 8 | 9 | ## Clone 10 | 11 | ``` 12 | git clone git@github.com:Louiszhai/canvas-draw.git 13 | ``` 14 | 15 | ## Build Setup 16 | 17 | ``` bash 18 | # install dependencies 19 | npm install 20 | 21 | # serve with hot reload at localhost:9588 22 | npm run dev 23 | 24 | # build for production with minification 25 | npm run build 26 | 27 | # build for production and view the bundle analyzer report 28 | npm run build --report 29 | ``` 30 | 31 | ## How to use 32 | 33 | canvas-draw基于vue-cli搭建,支持pc和mobile端。其核心代码为[src/utils/draw.js](https://github.com/Louiszhai/canvas-draw/blob/master/src/utils/draw.js),使用方法请参考[src/components/Canvas.vue](https://github.com/Louiszhai/canvas-draw/blob/master/src/components/Canvas.vue)。 34 | 35 | 以下是详细介绍。 36 | 37 | 支持mobile端时,由于手机尺寸限制,画布甚至包含画布的父级容器往往需要旋转至横屏展示,旋转角度通常有如下三种: 38 | 39 | ``` 40 | 0° 正常 41 | 90° 顺时针旋转90° 42 | 180° 顺时针旋转180° 43 | -90° 逆时针旋转90° 44 | ``` 45 | 46 | 第一步,旋转画布或其父级容器。 47 | 48 | ```js 49 | getStyle() { 50 | const d = document; 51 | const w = window.innerWidth || d.documentElement.clientWidth || d.body.clientWidth; 52 | const h = window.innerHeight || d.documentElement.clientHeight || d.body.clientHeight; 53 | let length = (h - w) / 2; 54 | let width = w; 55 | let height = h; 56 | 57 | switch (this.degree) { 58 | case -90: 59 | length = -length; 60 | case 90: 61 | width = h; 62 | height = w; 63 | break; 64 | default: 65 | length = 0; 66 | } 67 | return { 68 | transform: `rotate(${this.degree}deg) translate(${length}px,${length}px)`, // rotate后需要translate以适应新的宽高 69 | width: `${width}px`, 70 | height: `${height}px`, 71 | transformOrigin: 'center center', // 指定旋转的中心 72 | }; 73 | }, 74 | ``` 75 | 76 | 以上,获取了画布或其父级容器的样式,degree是旋转度数,默认为0。请注意,旋转只是视觉变化,此时canvas的坐标系并没有变化,依然是标准坐标系。因此需要重置画布坐标系。 77 | 78 | 第二步,初始化canvas画布,支持手写,并重置坐标系。 79 | 80 | ```js 81 | import Draw from '../utils/draw'; 82 | initCanvas() { 83 | const canvas = document.querySelector('canvas'); 84 | this.draw = new Draw(canvas, -this.degree); 85 | }, 86 | ``` 87 | 88 | 接下来就可以尝试手写了,由于画布可能旋转,并且坐标系可能被重置,因此擦除画布内容参考如下。 89 | 90 | ```js 91 | clear() { 92 | this.draw.clear(); 93 | }, 94 | ``` 95 | 96 | 绘画好的内容,需要这么下载(由于移动端webview限制,现仅支持PC端下载)。 97 | 98 | ```js 99 | download() { 100 | this.draw.downloadPNGImage(this.draw.getPNGImage()); 101 | }, 102 | ``` 103 | 104 | 当然,你可以将内容上传至服务器,参考如下。 105 | 106 | ```js 107 | upload() { 108 | const image = this.draw.getPNGImage(); 109 | const blob = this.draw.dataURLtoBlob(image); 110 | 111 | const url = ''; // 此处替换为你的远程服务器上传地址 112 | const successCallback = (response) => { 113 | console.log(response); 114 | }; 115 | const failureCallback = (error) => { 116 | console.log(error); 117 | }; 118 | this.draw.upload(blob, url, successCallback, failureCallback); 119 | }, 120 | ``` 121 | 122 | 除此之外,canvas-draw还内置了`scale`及`rotate`方法,你可以更灵活地调整画布大小、处理图片旋转等问题,祝使用愉快。 123 | 124 | ## More detail 125 | 126 | 最后,奉上canvas-draw的实现细节:[匠心打造canvas签名组件](http://louiszhai.github.io/2017/07/07/canvas-draw/)。 127 | 128 | ## License 129 | 130 | Released under [MIT](http://rem.mit-license.org/) LICENSE。 -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.prod.conf') 12 | 13 | var spinner = ora('building for production...') 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | console.log(chalk.cyan(' Build complete.\n')) 30 | console.log(chalk.yellow( 31 | ' Tip: built files are meant to be served over an HTTP server.\n' + 32 | ' Opening index.html over file:// won\'t work.\n' 33 | )) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | var shell = require('shelljs') 5 | function exec (cmd) { 6 | return require('child_process').execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | }, 15 | ] 16 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | var warnings = [] 27 | for (var i = 0; i < versionRequirements.length; i++) { 28 | var mod = versionRequirements[i] 29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 30 | warnings.push(mod.name + ': ' + 31 | chalk.red(mod.currentVersion) + ' should be ' + 32 | chalk.green(mod.versionRequirement) 33 | ) 34 | } 35 | } 36 | 37 | if (warnings.length) { 38 | console.log('') 39 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 40 | console.log() 41 | for (var i = 0; i < warnings.length; i++) { 42 | var warning = warnings[i] 43 | console.log(' ' + warning) 44 | } 45 | console.log() 46 | process.exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var config = require('../config') 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 6 | } 7 | 8 | var opn = require('opn') 9 | var path = require('path') 10 | var express = require('express') 11 | var webpack = require('webpack') 12 | var proxyMiddleware = require('http-proxy-middleware') 13 | var webpackConfig = process.env.NODE_ENV === 'testing' 14 | ? require('./webpack.prod.conf') 15 | : require('./webpack.dev.conf') 16 | 17 | // default port where dev server listens for incoming traffic 18 | var port = process.env.PORT || config.dev.port 19 | // automatically open browser, if not set will be false 20 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 21 | // Define HTTP proxies to your custom API backend 22 | // https://github.com/chimurai/http-proxy-middleware 23 | var proxyTable = config.dev.proxyTable 24 | 25 | var app = express() 26 | var compiler = webpack(webpackConfig) 27 | 28 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 29 | publicPath: webpackConfig.output.publicPath, 30 | quiet: true 31 | }) 32 | 33 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 34 | log: () => {} 35 | }) 36 | // force page reload when html-webpack-plugin template changes 37 | compiler.plugin('compilation', function (compilation) { 38 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 39 | hotMiddleware.publish({ action: 'reload' }) 40 | cb() 41 | }) 42 | }) 43 | 44 | // proxy api requests 45 | Object.keys(proxyTable).forEach(function (context) { 46 | var options = proxyTable[context] 47 | if (typeof options === 'string') { 48 | options = { target: options } 49 | } 50 | app.use(proxyMiddleware(options.filter || context, options)) 51 | }) 52 | 53 | // handle fallback for HTML5 history API 54 | app.use(require('connect-history-api-fallback')()) 55 | 56 | // serve webpack bundle output 57 | app.use(devMiddleware) 58 | 59 | // enable hot-reload and state-preserving 60 | // compilation error display 61 | app.use(hotMiddleware) 62 | 63 | // serve pure static assets 64 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 65 | app.use(staticPath, express.static('./static')) 66 | 67 | var uri = 'http://localhost:' + port 68 | 69 | var _resolve 70 | var readyPromise = new Promise(resolve => { 71 | _resolve = resolve 72 | }) 73 | 74 | console.log('> Starting dev server...') 75 | devMiddleware.waitUntilValid(() => { 76 | console.log('> Listening at ' + uri + '\n') 77 | // when env is testing, don't need open it 78 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 79 | // opn(uri) 80 | } 81 | _resolve() 82 | }) 83 | 84 | var server = app.listen(port) 85 | 86 | module.exports = { 87 | ready: readyPromise, 88 | close: () => { 89 | server.close() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | 15 | var cssLoader = { 16 | loader: 'css-loader', 17 | options: { 18 | minimize: process.env.NODE_ENV === 'production', 19 | sourceMap: options.sourceMap 20 | } 21 | } 22 | 23 | // generate loader string to be used with extract text plugin 24 | function generateLoaders (loader, loaderOptions) { 25 | var loaders = [cssLoader] 26 | if (loader) { 27 | loaders.push({ 28 | loader: loader + '-loader', 29 | options: Object.assign({}, loaderOptions, { 30 | sourceMap: options.sourceMap 31 | }) 32 | }) 33 | } 34 | 35 | // Extract CSS when that option is specified 36 | // (which is the case during production build) 37 | if (options.extract) { 38 | return ExtractTextPlugin.extract({ 39 | use: loaders, 40 | fallback: 'vue-style-loader' 41 | }) 42 | } else { 43 | return ['vue-style-loader'].concat(loaders) 44 | } 45 | } 46 | 47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 48 | return { 49 | css: generateLoaders(), 50 | postcss: generateLoaders(), 51 | less: generateLoaders('less'), 52 | sass: generateLoaders('sass', { indentedSyntax: true }), 53 | scss: generateLoaders('sass'), 54 | stylus: generateLoaders('stylus'), 55 | styl: generateLoaders('stylus') 56 | } 57 | } 58 | 59 | // Generate loaders for standalone style files (outside of .vue) 60 | exports.styleLoaders = function (options) { 61 | var output = [] 62 | var loaders = exports.cssLoaders(options) 63 | for (var extension in loaders) { 64 | var loader = loaders[extension] 65 | output.push({ 66 | test: new RegExp('\\.' + extension + '$'), 67 | use: loader 68 | }) 69 | } 70 | return output 71 | } 72 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var config = require('../config') 3 | var isProduction = process.env.NODE_ENV === 'production' 4 | 5 | module.exports = { 6 | loaders: utils.cssLoaders({ 7 | sourceMap: isProduction 8 | ? config.build.productionSourceMap 9 | : config.dev.cssSourceMap, 10 | extract: isProduction 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var config = require('../config') 4 | var vueLoaderConfig = require('./vue-loader.conf') 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | app: './src/main.js' 13 | }, 14 | output: { 15 | path: config.build.assetsRoot, 16 | filename: '[name].js', 17 | publicPath: process.env.NODE_ENV === 'production' 18 | ? config.build.assetsPublicPath 19 | : config.dev.assetsPublicPath 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.vue', '.json'], 23 | alias: { 24 | 'vue$': 'vue/dist/vue.esm.js', 25 | '@': resolve('src') 26 | } 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.(js|vue)$/, 32 | loader: 'eslint-loader', 33 | enforce: 'pre', 34 | include: [resolve('src'), resolve('test')], 35 | options: { 36 | formatter: require('eslint-friendly-formatter') 37 | } 38 | }, 39 | { 40 | test: /\.vue$/, 41 | loader: 'vue-loader', 42 | options: vueLoaderConfig 43 | }, 44 | { 45 | test: /\.js$/, 46 | loader: 'babel-loader', 47 | include: [resolve('src'), resolve('test')] 48 | }, 49 | { 50 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 51 | loader: 'url-loader', 52 | options: { 53 | limit: 10000, 54 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 55 | } 56 | }, 57 | { 58 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 59 | loader: 'url-loader', 60 | options: { 61 | limit: 10000, 62 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 63 | } 64 | } 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var merge = require('webpack-merge') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 8 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 12 | }) 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: '#cheap-module-eval-source-map', 20 | plugins: [ 21 | new webpack.DefinePlugin({ 22 | 'process.env': config.dev.env 23 | }), 24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoEmitOnErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index.html', 31 | inject: true 32 | }), 33 | new FriendlyErrorsPlugin() 34 | ] 35 | }) 36 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var CopyWebpackPlugin = require('copy-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 11 | 12 | var env = process.env.NODE_ENV === 'testing' 13 | ? require('../config/test.env') 14 | : config.build.env 15 | 16 | var webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true 21 | }) 22 | }, 23 | devtool: config.build.productionSourceMap ? '#source-map' : false, 24 | output: { 25 | path: config.build.assetsRoot, 26 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 27 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 31 | new webpack.DefinePlugin({ 32 | 'process.env': env 33 | }), 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compress: { 36 | warnings: false 37 | }, 38 | sourceMap: true 39 | }), 40 | // extract css into its own file 41 | new ExtractTextPlugin({ 42 | filename: utils.assetsPath('css/[name].[contenthash].css') 43 | }), 44 | // Compress extracted CSS. We are using this plugin so that possible 45 | // duplicated CSS from different components can be deduped. 46 | new OptimizeCSSPlugin({ 47 | cssProcessorOptions: { 48 | safe: true 49 | } 50 | }), 51 | // generate dist index.html with correct asset hash for caching. 52 | // you can customize output by editing /index.html 53 | // see https://github.com/ampedandwired/html-webpack-plugin 54 | new HtmlWebpackPlugin({ 55 | filename: process.env.NODE_ENV === 'testing' 56 | ? 'index.html' 57 | : config.build.index, 58 | template: 'index.html', 59 | inject: true, 60 | minify: { 61 | removeComments: true, 62 | collapseWhitespace: true, 63 | removeAttributeQuotes: true 64 | // more options: 65 | // https://github.com/kangax/html-minifier#options-quick-reference 66 | }, 67 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 68 | chunksSortMode: 'dependency' 69 | }), 70 | // split vendor js into its own file 71 | new webpack.optimize.CommonsChunkPlugin({ 72 | name: 'vendor', 73 | minChunks: function (module, count) { 74 | // any required modules inside node_modules are extracted to vendor 75 | return ( 76 | module.resource && 77 | /\.js$/.test(module.resource) && 78 | module.resource.indexOf( 79 | path.join(__dirname, '../node_modules') 80 | ) === 0 81 | ) 82 | } 83 | }), 84 | // extract webpack runtime and module manifest to its own file in order to 85 | // prevent vendor hash from being updated whenever app bundle is updated 86 | new webpack.optimize.CommonsChunkPlugin({ 87 | name: 'manifest', 88 | chunks: ['vendor'] 89 | }), 90 | // copy custom static assets 91 | new CopyWebpackPlugin([ 92 | { 93 | from: path.resolve(__dirname, '../static'), 94 | to: config.build.assetsSubDirectory, 95 | ignore: ['.*'] 96 | } 97 | ]) 98 | ] 99 | }) 100 | 101 | if (config.build.productionGzip) { 102 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 103 | 104 | webpackConfig.plugins.push( 105 | new CompressionWebpackPlugin({ 106 | asset: '[path].gz[query]', 107 | algorithm: 'gzip', 108 | test: new RegExp( 109 | '\\.(' + 110 | config.build.productionGzipExtensions.join('|') + 111 | ')$' 112 | ), 113 | threshold: 10240, 114 | minRatio: 0.8 115 | }) 116 | ) 117 | } 118 | 119 | if (config.build.bundleAnalyzerReport) { 120 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 121 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 122 | } 123 | 124 | module.exports = webpackConfig 125 | -------------------------------------------------------------------------------- /build/webpack.test.conf.js: -------------------------------------------------------------------------------- 1 | // This is the webpack config used for unit tests. 2 | 3 | var utils = require('./utils') 4 | var webpack = require('webpack') 5 | var merge = require('webpack-merge') 6 | var baseConfig = require('./webpack.base.conf') 7 | 8 | var webpackConfig = merge(baseConfig, { 9 | // use inline sourcemap for karma-sourcemap-loader 10 | module: { 11 | rules: utils.styleLoaders() 12 | }, 13 | devtool: '#inline-source-map', 14 | resolveLoader: { 15 | alias: { 16 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option 17 | // see discussion at https://github.com/vuejs/vue-loader/issues/724 18 | 'scss-loader': 'sass-loader' 19 | } 20 | }, 21 | plugins: [ 22 | new webpack.DefinePlugin({ 23 | 'process.env': require('../config/test.env') 24 | }) 25 | ] 26 | }) 27 | 28 | // no need for app entry during tests 29 | delete webpackConfig.entry 30 | 31 | module.exports = webpackConfig 32 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: '/', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'], 18 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report 23 | }, 24 | dev: { 25 | env: require('./dev.env'), 26 | port: 9588, 27 | autoOpenBrowser: true, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | proxyTable: {}, 31 | // CSS Sourcemaps off by default because relative paths are "buggy" 32 | // with this option, according to the CSS-Loader README 33 | // (https://github.com/webpack/css-loader#sourcemaps) 34 | // In our experience, they generally work as expected, 35 | // just be aware of this issue when enabling this option. 36 | cssSourceMap: false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) 7 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | canvas-draw 7 | 8 | 9 |
10 | 11 | 25 | 26 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "canvas-draw", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "array-find": { 7 | "version": "1.0.0", 8 | "resolved": "http://r.cnpmjs.org/array-find/download/array-find-1.0.0.tgz", 9 | "integrity": "sha1-bI4obRHtdoMn+OYuzuhzU8o+eLg=", 10 | "dev": true 11 | }, 12 | "asn1.js": { 13 | "version": "4.9.1", 14 | "resolved": "http://r.cnpmjs.org/asn1.js/download/asn1.js-4.9.1.tgz", 15 | "integrity": "sha1-SLokC0WpKA6UdImQull9IWYX/UA=", 16 | "dev": true 17 | }, 18 | "assert": { 19 | "version": "1.4.1", 20 | "resolved": "http://r.cnpmjs.org/assert/download/assert-1.4.1.tgz", 21 | "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", 22 | "dev": true 23 | }, 24 | "base64-js": { 25 | "version": "1.2.1", 26 | "resolved": "http://r.cnpmjs.org/base64-js/download/base64-js-1.2.1.tgz", 27 | "integrity": "sha1-qRlH2h9KUW6jjltOwOw3c2deCIY=", 28 | "dev": true 29 | }, 30 | "bn.js": { 31 | "version": "4.11.7", 32 | "resolved": "http://r.cnpmjs.org/bn.js/download/bn.js-4.11.7.tgz", 33 | "integrity": "sha1-3bBI5Q2UgnkAlME+s/z8gzznq0Y=", 34 | "dev": true 35 | }, 36 | "brorand": { 37 | "version": "1.1.0", 38 | "resolved": "http://r.cnpmjs.org/brorand/download/brorand-1.1.0.tgz", 39 | "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", 40 | "dev": true 41 | }, 42 | "browserify-aes": { 43 | "version": "1.0.6", 44 | "resolved": "http://r.cnpmjs.org/browserify-aes/download/browserify-aes-1.0.6.tgz", 45 | "integrity": "sha1-Xncl297x/Vkw1OurSFZ85FHEigo=", 46 | "dev": true 47 | }, 48 | "browserify-cipher": { 49 | "version": "1.0.0", 50 | "resolved": "http://r.cnpmjs.org/browserify-cipher/download/browserify-cipher-1.0.0.tgz", 51 | "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", 52 | "dev": true 53 | }, 54 | "browserify-des": { 55 | "version": "1.0.0", 56 | "resolved": "http://r.cnpmjs.org/browserify-des/download/browserify-des-1.0.0.tgz", 57 | "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", 58 | "dev": true 59 | }, 60 | "browserify-rsa": { 61 | "version": "4.0.1", 62 | "resolved": "http://r.cnpmjs.org/browserify-rsa/download/browserify-rsa-4.0.1.tgz", 63 | "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", 64 | "dev": true 65 | }, 66 | "browserify-sign": { 67 | "version": "4.0.4", 68 | "resolved": "http://r.cnpmjs.org/browserify-sign/download/browserify-sign-4.0.4.tgz", 69 | "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", 70 | "dev": true 71 | }, 72 | "browserify-zlib": { 73 | "version": "0.1.4", 74 | "resolved": "http://r.cnpmjs.org/browserify-zlib/download/browserify-zlib-0.1.4.tgz", 75 | "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", 76 | "dev": true 77 | }, 78 | "buffer": { 79 | "version": "4.9.1", 80 | "resolved": "http://r.cnpmjs.org/buffer/download/buffer-4.9.1.tgz", 81 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 82 | "dev": true 83 | }, 84 | "buffer-xor": { 85 | "version": "1.0.3", 86 | "resolved": "http://r.cnpmjs.org/buffer-xor/download/buffer-xor-1.0.3.tgz", 87 | "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", 88 | "dev": true 89 | }, 90 | "builtin-status-codes": { 91 | "version": "3.0.0", 92 | "resolved": "http://r.cnpmjs.org/builtin-status-codes/download/builtin-status-codes-3.0.0.tgz", 93 | "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", 94 | "dev": true 95 | }, 96 | "cipher-base": { 97 | "version": "1.0.3", 98 | "resolved": "http://r.cnpmjs.org/cipher-base/download/cipher-base-1.0.3.tgz", 99 | "integrity": "sha1-7qvxlEGc6QDaMBjCB9IS8qbfCgc=", 100 | "dev": true 101 | }, 102 | "console-browserify": { 103 | "version": "1.1.0", 104 | "resolved": "http://r.cnpmjs.org/console-browserify/download/console-browserify-1.1.0.tgz", 105 | "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", 106 | "dev": true 107 | }, 108 | "constants-browserify": { 109 | "version": "1.0.0", 110 | "resolved": "http://r.cnpmjs.org/constants-browserify/download/constants-browserify-1.0.0.tgz", 111 | "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", 112 | "dev": true 113 | }, 114 | "core-util-is": { 115 | "version": "1.0.2", 116 | "resolved": "http://r.cnpmjs.org/core-util-is/download/core-util-is-1.0.2.tgz", 117 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 118 | "dev": true 119 | }, 120 | "create-ecdh": { 121 | "version": "4.0.0", 122 | "resolved": "http://r.cnpmjs.org/create-ecdh/download/create-ecdh-4.0.0.tgz", 123 | "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", 124 | "dev": true 125 | }, 126 | "create-hash": { 127 | "version": "1.1.3", 128 | "resolved": "http://r.cnpmjs.org/create-hash/download/create-hash-1.1.3.tgz", 129 | "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", 130 | "dev": true 131 | }, 132 | "create-hmac": { 133 | "version": "1.1.6", 134 | "resolved": "http://r.cnpmjs.org/create-hmac/download/create-hmac-1.1.6.tgz", 135 | "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", 136 | "dev": true 137 | }, 138 | "crypto-browserify": { 139 | "version": "3.11.0", 140 | "resolved": "http://r.cnpmjs.org/crypto-browserify/download/crypto-browserify-3.11.0.tgz", 141 | "integrity": "sha1-NlKgkGq5sqfgw85mpAjpV6JIVSI=", 142 | "dev": true 143 | }, 144 | "date-now": { 145 | "version": "0.1.4", 146 | "resolved": "http://r.cnpmjs.org/date-now/download/date-now-0.1.4.tgz", 147 | "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", 148 | "dev": true 149 | }, 150 | "debug": { 151 | "version": "2.6.8", 152 | "resolved": "http://r.cnpmjs.org/debug/download/debug-2.6.8.tgz", 153 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", 154 | "dev": true 155 | }, 156 | "des.js": { 157 | "version": "1.0.0", 158 | "resolved": "http://r.cnpmjs.org/des.js/download/des.js-1.0.0.tgz", 159 | "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", 160 | "dev": true 161 | }, 162 | "diffie-hellman": { 163 | "version": "5.0.2", 164 | "resolved": "http://r.cnpmjs.org/diffie-hellman/download/diffie-hellman-5.0.2.tgz", 165 | "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", 166 | "dev": true 167 | }, 168 | "domain-browser": { 169 | "version": "1.1.7", 170 | "resolved": "http://r.cnpmjs.org/domain-browser/download/domain-browser-1.1.7.tgz", 171 | "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", 172 | "dev": true 173 | }, 174 | "elliptic": { 175 | "version": "6.4.0", 176 | "resolved": "http://r.cnpmjs.org/elliptic/download/elliptic-6.4.0.tgz", 177 | "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", 178 | "dev": true 179 | }, 180 | "enhanced-resolve": { 181 | "version": "0.9.1", 182 | "resolved": "http://r.cnpmjs.org/enhanced-resolve/download/enhanced-resolve-0.9.1.tgz", 183 | "integrity": "sha1-TW5omzcl+GCQknzMhs2fFjW4ni4=", 184 | "dev": true 185 | }, 186 | "eslint-config-airbnb-base": { 187 | "version": "11.2.0", 188 | "resolved": "http://r.cnpmjs.org/eslint-config-airbnb-base/download/eslint-config-airbnb-base-11.2.0.tgz", 189 | "integrity": "sha1-GancRIGib3CQRUXsBAEWh2AY+FM=", 190 | "dev": true 191 | }, 192 | "eslint-import-resolver-webpack": { 193 | "version": "0.8.3", 194 | "resolved": "http://r.cnpmjs.org/eslint-import-resolver-webpack/download/eslint-import-resolver-webpack-0.8.3.tgz", 195 | "integrity": "sha1-rWHijfN4pHRFnZU/JG/UP5JnU4U=", 196 | "dev": true 197 | }, 198 | "events": { 199 | "version": "1.1.1", 200 | "resolved": "http://r.cnpmjs.org/events/download/events-1.1.1.tgz", 201 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", 202 | "dev": true 203 | }, 204 | "evp_bytestokey": { 205 | "version": "1.0.0", 206 | "resolved": "http://r.cnpmjs.org/evp_bytestokey/download/evp_bytestokey-1.0.0.tgz", 207 | "integrity": "sha1-SXtmrZ/vZc18CKYYCCS6FHa2blM=", 208 | "dev": true 209 | }, 210 | "find-root": { 211 | "version": "0.1.2", 212 | "resolved": "http://r.cnpmjs.org/find-root/download/find-root-0.1.2.tgz", 213 | "integrity": "sha1-mNImfP8ZFsyvJ0OzoO6oHXnX3NE=", 214 | "dev": true 215 | }, 216 | "function-bind": { 217 | "version": "1.1.0", 218 | "resolved": "http://r.cnpmjs.org/function-bind/download/function-bind-1.1.0.tgz", 219 | "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=", 220 | "dev": true 221 | }, 222 | "graceful-fs": { 223 | "version": "4.1.11", 224 | "resolved": "http://r.cnpmjs.org/graceful-fs/download/graceful-fs-4.1.11.tgz", 225 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", 226 | "dev": true 227 | }, 228 | "has": { 229 | "version": "1.0.1", 230 | "resolved": "http://r.cnpmjs.org/has/download/has-1.0.1.tgz", 231 | "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", 232 | "dev": true 233 | }, 234 | "hash-base": { 235 | "version": "2.0.2", 236 | "resolved": "http://r.cnpmjs.org/hash-base/download/hash-base-2.0.2.tgz", 237 | "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", 238 | "dev": true 239 | }, 240 | "hash.js": { 241 | "version": "1.1.2", 242 | "resolved": "http://r.cnpmjs.org/hash.js/download/hash.js-1.1.2.tgz", 243 | "integrity": "sha1-v1yIeCXP5Aue/ee/Eb0tsm5r8Bs=", 244 | "dev": true, 245 | "dependencies": { 246 | "inherits": { 247 | "version": "2.0.3", 248 | "resolved": "http://r.cnpmjs.org/inherits/download/inherits-2.0.3.tgz", 249 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 250 | "dev": true 251 | } 252 | } 253 | }, 254 | "hmac-drbg": { 255 | "version": "1.0.1", 256 | "resolved": "http://r.cnpmjs.org/hmac-drbg/download/hmac-drbg-1.0.1.tgz", 257 | "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", 258 | "dev": true 259 | }, 260 | "https-browserify": { 261 | "version": "0.0.1", 262 | "resolved": "http://r.cnpmjs.org/https-browserify/download/https-browserify-0.0.1.tgz", 263 | "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=", 264 | "dev": true 265 | }, 266 | "ieee754": { 267 | "version": "1.1.8", 268 | "resolved": "http://r.cnpmjs.org/ieee754/download/ieee754-1.1.8.tgz", 269 | "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", 270 | "dev": true 271 | }, 272 | "indexof": { 273 | "version": "0.0.1", 274 | "resolved": "http://r.cnpmjs.org/indexof/download/indexof-0.0.1.tgz", 275 | "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", 276 | "dev": true 277 | }, 278 | "inherits": { 279 | "version": "2.0.1", 280 | "resolved": "http://r.cnpmjs.org/inherits/download/inherits-2.0.1.tgz", 281 | "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", 282 | "dev": true 283 | }, 284 | "interpret": { 285 | "version": "1.0.3", 286 | "resolved": "http://r.cnpmjs.org/interpret/download/interpret-1.0.3.tgz", 287 | "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=", 288 | "dev": true 289 | }, 290 | "is-absolute": { 291 | "version": "0.2.6", 292 | "resolved": "http://r.cnpmjs.org/is-absolute/download/is-absolute-0.2.6.tgz", 293 | "integrity": "sha1-IN5p89uULvLYe5wto28XIjWxtes=", 294 | "dev": true 295 | }, 296 | "is-relative": { 297 | "version": "0.2.1", 298 | "resolved": "http://r.cnpmjs.org/is-relative/download/is-relative-0.2.1.tgz", 299 | "integrity": "sha1-0n9MfVFtF1+2ENuEu+7yPDvJeqU=", 300 | "dev": true 301 | }, 302 | "is-unc-path": { 303 | "version": "0.1.2", 304 | "resolved": "http://r.cnpmjs.org/is-unc-path/download/is-unc-path-0.1.2.tgz", 305 | "integrity": "sha1-arBTpyVzwQJQ/0FqOBTDUXivObk=", 306 | "dev": true 307 | }, 308 | "is-windows": { 309 | "version": "0.2.0", 310 | "resolved": "http://r.cnpmjs.org/is-windows/download/is-windows-0.2.0.tgz", 311 | "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=", 312 | "dev": true 313 | }, 314 | "isarray": { 315 | "version": "1.0.0", 316 | "resolved": "http://r.cnpmjs.org/isarray/download/isarray-1.0.0.tgz", 317 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 318 | "dev": true 319 | }, 320 | "lodash._baseget": { 321 | "version": "3.7.2", 322 | "resolved": "http://r.cnpmjs.org/lodash._baseget/download/lodash._baseget-3.7.2.tgz", 323 | "integrity": "sha1-G2rh1frPPCVTI1ChPBGXy4u2dPQ=", 324 | "dev": true 325 | }, 326 | "lodash._topath": { 327 | "version": "3.8.1", 328 | "resolved": "http://r.cnpmjs.org/lodash._topath/download/lodash._topath-3.8.1.tgz", 329 | "integrity": "sha1-PsXiYGAU9MuX91X+aRTt2L/ADqw=", 330 | "dev": true 331 | }, 332 | "lodash.get": { 333 | "version": "3.7.0", 334 | "resolved": "http://r.cnpmjs.org/lodash.get/download/lodash.get-3.7.0.tgz", 335 | "integrity": "sha1-POaK4skWg7KBzFOUEoMDy/deaR8=", 336 | "dev": true 337 | }, 338 | "lodash.isarray": { 339 | "version": "3.0.4", 340 | "resolved": "http://r.cnpmjs.org/lodash.isarray/download/lodash.isarray-3.0.4.tgz", 341 | "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", 342 | "dev": true 343 | }, 344 | "memory-fs": { 345 | "version": "0.2.0", 346 | "resolved": "http://r.cnpmjs.org/memory-fs/download/memory-fs-0.2.0.tgz", 347 | "integrity": "sha1-8rslNovBIeORwlIN6Slpyu4KApA=", 348 | "dev": true 349 | }, 350 | "miller-rabin": { 351 | "version": "4.0.0", 352 | "resolved": "http://r.cnpmjs.org/miller-rabin/download/miller-rabin-4.0.0.tgz", 353 | "integrity": "sha1-SmL7HUKTPAVYOYL0xxb2+55sbT0=", 354 | "dev": true 355 | }, 356 | "minimalistic-assert": { 357 | "version": "1.0.0", 358 | "resolved": "http://r.cnpmjs.org/minimalistic-assert/download/minimalistic-assert-1.0.0.tgz", 359 | "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=", 360 | "dev": true 361 | }, 362 | "minimalistic-crypto-utils": { 363 | "version": "1.0.1", 364 | "resolved": "http://r.cnpmjs.org/minimalistic-crypto-utils/download/minimalistic-crypto-utils-1.0.1.tgz", 365 | "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", 366 | "dev": true 367 | }, 368 | "ms": { 369 | "version": "2.0.0", 370 | "resolved": "http://r.cnpmjs.org/ms/download/ms-2.0.0.tgz", 371 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 372 | "dev": true 373 | }, 374 | "node-libs-browser": { 375 | "version": "1.1.1", 376 | "resolved": "http://r.cnpmjs.org/node-libs-browser/download/node-libs-browser-1.1.1.tgz", 377 | "integrity": "sha1-KjgkOr7dff/NB6l8mspWaJdab+o=", 378 | "dev": true 379 | }, 380 | "os-browserify": { 381 | "version": "0.2.1", 382 | "resolved": "http://r.cnpmjs.org/os-browserify/download/os-browserify-0.2.1.tgz", 383 | "integrity": "sha1-Y/xMzuXS13Y9Jrv4YBB45sLgBE8=", 384 | "dev": true 385 | }, 386 | "pako": { 387 | "version": "0.2.9", 388 | "resolved": "http://r.cnpmjs.org/pako/download/pako-0.2.9.tgz", 389 | "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", 390 | "dev": true 391 | }, 392 | "parse-asn1": { 393 | "version": "5.1.0", 394 | "resolved": "http://r.cnpmjs.org/parse-asn1/download/parse-asn1-5.1.0.tgz", 395 | "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", 396 | "dev": true 397 | }, 398 | "path-browserify": { 399 | "version": "0.0.0", 400 | "resolved": "http://r.cnpmjs.org/path-browserify/download/path-browserify-0.0.0.tgz", 401 | "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", 402 | "dev": true 403 | }, 404 | "path-parse": { 405 | "version": "1.0.5", 406 | "resolved": "http://r.cnpmjs.org/path-parse/download/path-parse-1.0.5.tgz", 407 | "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", 408 | "dev": true 409 | }, 410 | "pbkdf2": { 411 | "version": "3.0.12", 412 | "resolved": "http://r.cnpmjs.org/pbkdf2/download/pbkdf2-3.0.12.tgz", 413 | "integrity": "sha1-vjZ4XFBn6kjYBv+SMojF91C2uKI=", 414 | "dev": true 415 | }, 416 | "process": { 417 | "version": "0.11.10", 418 | "resolved": "http://r.cnpmjs.org/process/download/process-0.11.10.tgz", 419 | "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", 420 | "dev": true 421 | }, 422 | "process-nextick-args": { 423 | "version": "1.0.7", 424 | "resolved": "http://r.cnpmjs.org/process-nextick-args/download/process-nextick-args-1.0.7.tgz", 425 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", 426 | "dev": true 427 | }, 428 | "public-encrypt": { 429 | "version": "4.0.0", 430 | "resolved": "http://r.cnpmjs.org/public-encrypt/download/public-encrypt-4.0.0.tgz", 431 | "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", 432 | "dev": true 433 | }, 434 | "punycode": { 435 | "version": "1.4.1", 436 | "resolved": "http://r.cnpmjs.org/punycode/download/punycode-1.4.1.tgz", 437 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", 438 | "dev": true 439 | }, 440 | "querystring": { 441 | "version": "0.2.0", 442 | "resolved": "http://r.cnpmjs.org/querystring/download/querystring-0.2.0.tgz", 443 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", 444 | "dev": true 445 | }, 446 | "querystring-es3": { 447 | "version": "0.2.1", 448 | "resolved": "http://r.cnpmjs.org/querystring-es3/download/querystring-es3-0.2.1.tgz", 449 | "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", 450 | "dev": true 451 | }, 452 | "randombytes": { 453 | "version": "2.0.5", 454 | "resolved": "http://r.cnpmjs.org/randombytes/download/randombytes-2.0.5.tgz", 455 | "integrity": "sha1-3ACaJGuNCaF3tLegrne8Vw9LG3k=", 456 | "dev": true 457 | }, 458 | "readable-stream": { 459 | "version": "2.3.3", 460 | "resolved": "http://r.cnpmjs.org/readable-stream/download/readable-stream-2.3.3.tgz", 461 | "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", 462 | "dev": true, 463 | "dependencies": { 464 | "inherits": { 465 | "version": "2.0.3", 466 | "resolved": "http://r.cnpmjs.org/inherits/download/inherits-2.0.3.tgz", 467 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 468 | "dev": true 469 | }, 470 | "string_decoder": { 471 | "version": "1.0.3", 472 | "resolved": "http://r.cnpmjs.org/string_decoder/download/string_decoder-1.0.3.tgz", 473 | "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", 474 | "dev": true 475 | } 476 | } 477 | }, 478 | "resolve": { 479 | "version": "1.3.3", 480 | "resolved": "http://r.cnpmjs.org/resolve/download/resolve-1.3.3.tgz", 481 | "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=", 482 | "dev": true 483 | }, 484 | "ripemd160": { 485 | "version": "2.0.1", 486 | "resolved": "http://r.cnpmjs.org/ripemd160/download/ripemd160-2.0.1.tgz", 487 | "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", 488 | "dev": true 489 | }, 490 | "safe-buffer": { 491 | "version": "5.1.1", 492 | "resolved": "http://r.cnpmjs.org/safe-buffer/download/safe-buffer-5.1.1.tgz", 493 | "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=", 494 | "dev": true 495 | }, 496 | "semver": { 497 | "version": "5.3.0", 498 | "resolved": "http://r.cnpmjs.org/semver/download/semver-5.3.0.tgz", 499 | "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", 500 | "dev": true 501 | }, 502 | "sha.js": { 503 | "version": "2.4.8", 504 | "resolved": "http://r.cnpmjs.org/sha.js/download/sha.js-2.4.8.tgz", 505 | "integrity": "sha1-NwaMLEdra69ALRSknGf1l5IfY08=", 506 | "dev": true 507 | }, 508 | "stream-browserify": { 509 | "version": "2.0.1", 510 | "resolved": "http://r.cnpmjs.org/stream-browserify/download/stream-browserify-2.0.1.tgz", 511 | "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", 512 | "dev": true 513 | }, 514 | "stream-http": { 515 | "version": "2.7.2", 516 | "resolved": "http://r.cnpmjs.org/stream-http/download/stream-http-2.7.2.tgz", 517 | "integrity": "sha1-QKBQ7I3DtTsz2ZCUFcAsC/Gr+60=", 518 | "dev": true 519 | }, 520 | "string_decoder": { 521 | "version": "0.10.31", 522 | "resolved": "http://r.cnpmjs.org/string_decoder/download/string_decoder-0.10.31.tgz", 523 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", 524 | "dev": true 525 | }, 526 | "tapable": { 527 | "version": "0.1.10", 528 | "resolved": "http://r.cnpmjs.org/tapable/download/tapable-0.1.10.tgz", 529 | "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=", 530 | "dev": true 531 | }, 532 | "timers-browserify": { 533 | "version": "1.4.2", 534 | "resolved": "http://r.cnpmjs.org/timers-browserify/download/timers-browserify-1.4.2.tgz", 535 | "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", 536 | "dev": true 537 | }, 538 | "to-arraybuffer": { 539 | "version": "1.0.1", 540 | "resolved": "http://r.cnpmjs.org/to-arraybuffer/download/to-arraybuffer-1.0.1.tgz", 541 | "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", 542 | "dev": true 543 | }, 544 | "tty-browserify": { 545 | "version": "0.0.0", 546 | "resolved": "http://r.cnpmjs.org/tty-browserify/download/tty-browserify-0.0.0.tgz", 547 | "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", 548 | "dev": true 549 | }, 550 | "unc-path-regex": { 551 | "version": "0.1.2", 552 | "resolved": "http://r.cnpmjs.org/unc-path-regex/download/unc-path-regex-0.1.2.tgz", 553 | "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", 554 | "dev": true 555 | }, 556 | "url": { 557 | "version": "0.11.0", 558 | "resolved": "http://r.cnpmjs.org/url/download/url-0.11.0.tgz", 559 | "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", 560 | "dev": true, 561 | "dependencies": { 562 | "punycode": { 563 | "version": "1.3.2", 564 | "resolved": "http://r.cnpmjs.org/punycode/download/punycode-1.3.2.tgz", 565 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", 566 | "dev": true 567 | } 568 | } 569 | }, 570 | "util": { 571 | "version": "0.10.3", 572 | "resolved": "http://r.cnpmjs.org/util/download/util-0.10.3.tgz", 573 | "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", 574 | "dev": true 575 | }, 576 | "util-deprecate": { 577 | "version": "1.0.2", 578 | "resolved": "http://r.cnpmjs.org/util-deprecate/download/util-deprecate-1.0.2.tgz", 579 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 580 | "dev": true 581 | }, 582 | "vm-browserify": { 583 | "version": "0.0.4", 584 | "resolved": "http://r.cnpmjs.org/vm-browserify/download/vm-browserify-0.0.4.tgz", 585 | "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", 586 | "dev": true 587 | }, 588 | "xtend": { 589 | "version": "4.0.1", 590 | "resolved": "http://r.cnpmjs.org/xtend/download/xtend-4.0.1.tgz", 591 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", 592 | "dev": true 593 | } 594 | } 595 | } 596 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "canvas-draw", 3 | "version": "1.0.0", 4 | "description": "use canvas to draw", 5 | "author": "kingszhai@gmail.com", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "start": "node build/dev-server.js", 10 | "build": "node build/build.js", 11 | "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs" 12 | }, 13 | "dependencies": { 14 | "eslint-plugin-import": "^2.6.1", 15 | "vue": "^2.3.3", 16 | "vue-router": "^2.3.1", 17 | "webpack-merge": "^4.1.0" 18 | }, 19 | "devDependencies": { 20 | "autoprefixer": "^6.7.2", 21 | "babel-core": "^6.22.1", 22 | "babel-eslint": "^7.1.1", 23 | "babel-loader": "^6.2.10", 24 | "babel-plugin-istanbul": "^4.1.1", 25 | "babel-plugin-transform-runtime": "^6.22.0", 26 | "babel-preset-env": "^1.3.2", 27 | "babel-preset-stage-2": "^6.22.0", 28 | "babel-register": "^6.22.0", 29 | "chai": "^3.5.0", 30 | "chalk": "^1.1.3", 31 | "chromedriver": "^2.27.2", 32 | "connect-history-api-fallback": "^1.3.0", 33 | "copy-webpack-plugin": "^4.0.1", 34 | "cross-env": "^4.0.0", 35 | "cross-spawn": "^5.0.1", 36 | "css-loader": "^0.28.0", 37 | "eslint": "^3.19.0", 38 | "eslint-config-airbnb-base": "^11.2.0", 39 | "eslint-config-standard": "^6.2.1", 40 | "eslint-friendly-formatter": "^2.0.7", 41 | "eslint-import-resolver-webpack": "^0.8.3", 42 | "eslint-loader": "^1.7.1", 43 | "eslint-plugin-html": "^2.0.0", 44 | "eslint-plugin-promise": "^3.4.0", 45 | "eslint-plugin-standard": "^2.0.1", 46 | "eventsource-polyfill": "^0.9.6", 47 | "express": "^4.14.1", 48 | "extract-text-webpack-plugin": "^2.0.0", 49 | "file-loader": "^0.11.1", 50 | "friendly-errors-webpack-plugin": "^1.1.3", 51 | "html-webpack-plugin": "^2.28.0", 52 | "http-proxy-middleware": "^0.17.3", 53 | "inject-loader": "^3.0.0", 54 | "karma": "^1.4.1", 55 | "karma-coverage": "^1.1.1", 56 | "karma-mocha": "^1.3.0", 57 | "karma-phantomjs-launcher": "^1.0.2", 58 | "karma-phantomjs-shim": "^1.4.0", 59 | "karma-sinon-chai": "^1.3.1", 60 | "karma-sourcemap-loader": "^0.3.7", 61 | "karma-spec-reporter": "0.0.30", 62 | "karma-webpack": "^2.0.2", 63 | "lolex": "^1.5.2", 64 | "mocha": "^3.2.0", 65 | "nightwatch": "^0.9.12", 66 | "opn": "^4.0.2", 67 | "optimize-css-assets-webpack-plugin": "^1.3.0", 68 | "ora": "^1.2.0", 69 | "phantomjs-prebuilt": "^2.1.14", 70 | "rimraf": "^2.6.0", 71 | "selenium-server": "^3.0.1", 72 | "semver": "^5.3.0", 73 | "shelljs": "^0.7.6", 74 | "sinon": "^2.1.0", 75 | "sinon-chai": "^2.8.0", 76 | "url-loader": "^0.5.8", 77 | "vue-loader": "^12.1.0", 78 | "vue-style-loader": "^3.0.1", 79 | "vue-template-compiler": "^2.3.3", 80 | "webpack": "^2.6.1", 81 | "webpack-bundle-analyzer": "^2.2.1", 82 | "webpack-dev-middleware": "^1.10.0", 83 | "webpack-hot-middleware": "^2.18.0" 84 | }, 85 | "engines": { 86 | "node": ">= 4.0.0", 87 | "npm": ">= 3.0.0" 88 | }, 89 | "browserslist": [ 90 | "> 1%", 91 | "last 2 versions", 92 | "not ie <= 8" 93 | ] 94 | } 95 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 27 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GavinZhuLei/canvas-draw/c04de9e42426c294bed2eccdef4822e5ed986c3a/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/Canvas.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 114 | 115 | 137 | -------------------------------------------------------------------------------- /src/components/sign.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 111 | 112 | 152 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App'; 3 | import router from './router'; 4 | 5 | Vue.config.productionTip = false; 6 | 7 | /* eslint-disable no-new */ 8 | new Vue({ 9 | el: '#app', 10 | router, 11 | template: '', 12 | components: { App }, 13 | }); 14 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import Canvas from '@/components/canvas'; 4 | import Sign from '@/components/sign'; 5 | 6 | Vue.use(Router); 7 | 8 | export default new Router({ 9 | routes: [ 10 | { 11 | path: '/', 12 | name: 'Canvas', 13 | component: Canvas, 14 | }, 15 | { 16 | path: '/sign', 17 | name: 'Sign', 18 | component: Sign, 19 | }, 20 | ], 21 | }); 22 | -------------------------------------------------------------------------------- /src/utils/draw.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by louizhai on 17/6/30. 3 | * description: Use canvas to draw. 4 | */ 5 | function Draw(canvas, degree, config = {}) { 6 | if (!(this instanceof Draw)) { 7 | return new Draw(canvas, config); 8 | } 9 | if (!canvas) { 10 | return; 11 | } 12 | let { width, height } = window.getComputedStyle(canvas, null); 13 | width = width.replace('px', ''); 14 | height = height.replace('px', ''); 15 | 16 | this.canvas = canvas; 17 | this.context = canvas.getContext('2d'); 18 | this.width = width; 19 | this.height = height; 20 | const context = this.context; 21 | 22 | // 根据设备像素比优化canvas绘图 23 | const devicePixelRatio = window.devicePixelRatio; 24 | if (devicePixelRatio) { 25 | canvas.style.width = `${width}px`; 26 | canvas.style.height = `${height}px`; 27 | canvas.height = height * devicePixelRatio; 28 | canvas.width = width * devicePixelRatio; 29 | context.scale(devicePixelRatio, devicePixelRatio); 30 | } else { 31 | canvas.width = width; 32 | canvas.height = height; 33 | } 34 | 35 | context.lineWidth = 6; 36 | context.strokeStyle = 'black'; 37 | context.lineCap = 'round'; 38 | context.lineJoin = 'round'; 39 | Object.assign(context, config); 40 | 41 | const { left, top } = canvas.getBoundingClientRect(); 42 | const point = {}; 43 | const isMobile = /phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone/i.test(navigator.userAgent); 44 | // 移动端性能太弱, 去掉模糊以提高手写渲染速度 45 | if (!isMobile) { 46 | context.shadowBlur = 1; 47 | context.shadowColor = 'black'; 48 | } 49 | let pressed = false; 50 | 51 | const paint = (signal) => { 52 | switch (signal) { 53 | case 1: 54 | context.beginPath(); 55 | context.moveTo(point.x, point.y); 56 | case 2: 57 | context.lineTo(point.x, point.y); 58 | context.stroke(); 59 | break; 60 | default: 61 | } 62 | }; 63 | const create = signal => (e) => { 64 | e.preventDefault(); 65 | if (signal === 1) { 66 | pressed = true; 67 | } 68 | if (signal === 1 || pressed) { 69 | e = isMobile ? e.touches[0] : e; 70 | point.x = e.clientX - left; 71 | point.y = e.clientY - top; 72 | paint(signal); 73 | } 74 | }; 75 | const start = create(1); 76 | const move = create(2); 77 | const requestAnimationFrame = window.requestAnimationFrame; 78 | const optimizedMove = requestAnimationFrame ? (e) => { 79 | requestAnimationFrame(() => { 80 | move(e); 81 | }); 82 | } : move; 83 | 84 | if (isMobile) { 85 | canvas.addEventListener('touchstart', start); 86 | canvas.addEventListener('touchmove', optimizedMove); 87 | } else { 88 | canvas.addEventListener('mousedown', start); 89 | canvas.addEventListener('mousemove', optimizedMove); 90 | ['mouseup', 'mouseleave'].forEach((event) => { 91 | canvas.addEventListener(event, () => { 92 | pressed = false; 93 | }); 94 | }); 95 | } 96 | 97 | // 重置画布坐标系 98 | if (typeof degree === 'number') { 99 | this.degree = degree; 100 | context.rotate((degree * Math.PI) / 180); 101 | switch (degree) { 102 | case -90: 103 | context.translate(-height, 0); 104 | break; 105 | case 90: 106 | context.translate(0, -width); 107 | break; 108 | case -180: 109 | case 180: 110 | context.translate(-width, -height); 111 | break; 112 | default: 113 | } 114 | } 115 | } 116 | Draw.prototype = { 117 | scale(width, height, canvas = this.canvas) { 118 | const w = canvas.width; 119 | const h = canvas.height; 120 | width = width || w; 121 | height = height || h; 122 | if (width !== w || height !== h) { 123 | const tmpCanvas = document.createElement('canvas'); 124 | const tmpContext = tmpCanvas.getContext('2d'); 125 | tmpCanvas.width = width; 126 | tmpCanvas.height = height; 127 | tmpContext.drawImage(canvas, 0, 0, w, h, 0, 0, width, height); 128 | canvas = tmpCanvas; 129 | } 130 | return canvas; 131 | }, 132 | rotate(degree, image = this.canvas) { 133 | degree = ~~degree; 134 | if (degree !== 0) { 135 | const maxDegree = 180; 136 | const minDegree = -90; 137 | if (degree > maxDegree) { 138 | degree = maxDegree; 139 | } else if (degree < minDegree) { 140 | degree = minDegree; 141 | } 142 | 143 | const canvas = document.createElement('canvas'); 144 | const context = canvas.getContext('2d'); 145 | const height = image.height; 146 | const width = image.width; 147 | const degreePI = (degree * Math.PI) / 180; 148 | 149 | switch (degree) { 150 | // 逆时针旋转90° 151 | case -90: 152 | canvas.width = height; 153 | canvas.height = width; 154 | context.rotate(degreePI); 155 | context.drawImage(image, -width, 0); 156 | break; 157 | // 顺时针旋转90° 158 | case 90: 159 | canvas.width = height; 160 | canvas.height = width; 161 | context.rotate(degreePI); 162 | context.drawImage(image, 0, -height); 163 | break; 164 | // 顺时针旋转180° 165 | case 180: 166 | canvas.width = width; 167 | canvas.height = height; 168 | context.rotate(degreePI); 169 | context.drawImage(image, -width, -height); 170 | break; 171 | default: 172 | } 173 | image = canvas; 174 | } 175 | return image; 176 | }, 177 | getPNGImage(canvas = this.canvas) { 178 | return canvas.toDataURL('image/png'); 179 | }, 180 | getJPGImage(canvas = this.canvas) { 181 | return canvas.toDataURL('image/jpeg', 0.5); 182 | }, 183 | downloadPNGImage(image) { 184 | const url = image.replace('image/png', 'image/octet-stream;Content-Disposition:attachment;filename=test.png'); 185 | window.location.href = url; 186 | }, 187 | dataURLtoBlob(dataURL) { 188 | const arr = dataURL.split(','); 189 | const mime = arr[0].match(/:(.*?);/)[1]; 190 | const bStr = atob(arr[1]); 191 | let n = bStr.length; 192 | const u8arr = new Uint8Array(n); 193 | while (n--) { 194 | u8arr[n] = bStr.charCodeAt(n); 195 | } 196 | return new Blob([u8arr], { type: mime }); 197 | }, 198 | clear() { 199 | let width; 200 | let height; 201 | switch (this.degree) { 202 | case -90: 203 | case 90: 204 | width = this.height; 205 | height = this.width; 206 | break; 207 | default: 208 | width = this.width; 209 | height = this.height; 210 | } 211 | this.context.clearRect(0, 0, width, height); 212 | }, 213 | upload(blob, url, success, failure) { 214 | const formData = new FormData(); 215 | const xhr = new XMLHttpRequest(); 216 | xhr.withCredentials = true; 217 | formData.append('image', blob, 'sign'); 218 | 219 | xhr.open('POST', url, true); 220 | xhr.onload = () => { 221 | if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { 222 | success(xhr.responseText); 223 | } else { 224 | failure(); 225 | } 226 | }; 227 | xhr.onerror = (e) => { 228 | if (typeof failure === 'function') { 229 | failure(e); 230 | } else { 231 | console.log(`upload img error: ${e}`); 232 | } 233 | }; 234 | xhr.send(formData); 235 | }, 236 | }; 237 | export default Draw; 238 | -------------------------------------------------------------------------------- /src/utils/iload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by louiszhai on 17/6/19. 3 | * description: Asynchronous sequential loading resources. 4 | */ 5 | const head = document.getElementsByTagName('head').item(0); 6 | 7 | // 加载script标签 8 | function loadScript(path, isSync, callback) { 9 | const script = document.createElement('script'); 10 | script.async = !!isSync; 11 | script.src = path; 12 | if (typeof callback === 'function') { 13 | script.addEventListener('load', callback); 14 | } 15 | head.appendChild(script); 16 | } 17 | 18 | // 异步加载顺序执行多个script脚本 19 | const iload = (...rest) => { 20 | let pathLength = rest.length; 21 | let callback; 22 | if (pathLength === 0) { 23 | return false; 24 | } else if (typeof rest[pathLength - 1] === 'function') { 25 | pathLength--; 26 | callback = rest[pathLength]; 27 | } 28 | if (pathLength === 0) { 29 | return false; 30 | } else if (pathLength === 1) { 31 | loadScript(rest[0], false, callback); 32 | } else { 33 | for (let i = 0; i < pathLength; i++) { 34 | if (i === pathLength - 1) { 35 | loadScript(rest[i], true, callback); 36 | } else { 37 | loadScript(rest[i], true); 38 | } 39 | } 40 | } 41 | }; 42 | 43 | export default { 44 | install: (Vue, name = 'iload') => { 45 | Object.defineProperty(Vue.prototype, name, { value: iload }); 46 | }, 47 | }; 48 | 49 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GavinZhuLei/canvas-draw/c04de9e42426c294bed2eccdef4822e5ed986c3a/static/.gitkeep --------------------------------------------------------------------------------