├── .gitignore ├── example ├── loading.gif ├── thumb.jpg ├── thumb2.jpg ├── zhihu.jpg ├── zhihu.js ├── zhihu.html ├── demo.css └── demo.html ├── .babelrc ├── dist ├── canvastools.css.map ├── canvastools.min.js ├── canvastools.min.css ├── canvastools.css ├── canvastools.js └── canvastools.js.map ├── postcss.config.js ├── src ├── js │ ├── closet.js │ ├── template.js │ ├── utils.js │ └── main.js └── scss │ └── canvastools.scss ├── LICENSE ├── README.md ├── package.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /node_modules 3 | /package-lock.json 4 | /.sass-cache -------------------------------------------------------------------------------- /example/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S-mohan/canvasTools/HEAD/example/loading.gif -------------------------------------------------------------------------------- /example/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S-mohan/canvasTools/HEAD/example/thumb.jpg -------------------------------------------------------------------------------- /example/thumb2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S-mohan/canvasTools/HEAD/example/thumb2.jpg -------------------------------------------------------------------------------- /example/zhihu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S-mohan/canvasTools/HEAD/example/zhihu.jpg -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["babel-plugin-add-module-exports"] 4 | } -------------------------------------------------------------------------------- /dist/canvastools.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"canvastools.css","sourceRoot":""} -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "plugins": { 3 | // to edit target browsers: use "browserlist" field in package.json 4 | "autoprefixer": { 5 | browsers: ['last 2 versions'] 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/js/closet.js: -------------------------------------------------------------------------------- 1 | //Element.closet Polyfill 2 | //https://developer.mozilla.org/en-US/docs/Web/API/Element/closest 3 | if (window.Element && !Element.prototype.closest) { 4 | Element.prototype.closest = 5 | function(s) { 6 | var matches = (this.document || this.ownerDocument).querySelectorAll(s), 7 | i, 8 | el = this; 9 | do { 10 | i = matches.length; 11 | while (--i >= 0 && matches.item(i) !== el) {}; 12 | } while ((i < 0) && (el = el.parentElement)); 13 | return el; 14 | }; 15 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Smohan 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 | # CanvasTools 2 | Screenshot tool based on canvas 3 | 4 | > 基于Canvas的截图辅助工具集。包含矩形,椭圆,画笔,文字等多种工具以及撤销,保存等各种功能 5 | 6 | ![CanvasTools](https://img.smohan.net/project/d73e2e41950ea7d342a45fc6a57bd291.jpg) 7 | 8 | #### [项目地址](https://smohan.net/lab/canvastools) 9 | #### [DEMO](https://s-mohan.github.io/demo/canvastools/demo.html) 10 | #### [仿知乎截图反馈](https://s-mohan.github.io/demo/canvastools/zhihu.html) 11 | 12 | 项目构思来源于知乎建议反馈功能中的截图反馈。期初看到该功能,惊为天人,不得不佩服知乎对用户体验的细节追求。然后又对比和参考了QQ截图(基本样式来源于此),花了三天时间完成了`CanvasTools`项目的构建。 13 | 截止目前,在完成了大部分功能的同时,也学习了大部分的`Canvas API`: 14 | 15 | ### 可用功能 16 | 17 | - 矩形工具 18 | - 椭圆工具 19 | - 画笔工具 20 | - 文字工具 21 | - 马赛克工具 22 | - 撤销功能 23 | - 保存功能(`IE10+`) 24 | 25 | ### 待完成功能 26 | 27 | - 橡皮擦工具 28 | 29 | ### 如何使用 30 | 31 | #### 1.使用NPM 32 | 33 | ```javascript 34 | npm install 35 | //dev 36 | npm run dev 37 | //build 38 | npm run build 39 | ``` 40 | 41 | 42 | #### 2.直接使用 43 | 44 | 1.页面`head`标签中引入 45 | ```html 46 | 47 | ``` 48 | 2.在`body`中创建`canvas`以及用来放置工具条的容器 49 | ```html 50 | ... 51 | 52 |
53 | ... 54 | ``` 55 | 3.在 `` 之前调用 56 | ```html 57 | 58 | 63 | ``` 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CanvasTools", 3 | "version": "1.0.0", 4 | "description": "Screenshot tool based on canvas", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --env build", 8 | "dev": "webpack --progress --colors --watch --env dev", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/S-mohan/canvasTools.git" 14 | }, 15 | "keywords": [ 16 | "canvas", 17 | "screenshot", 18 | "captures", 19 | "tools", 20 | "tools", 21 | "javascript" 22 | ], 23 | "author": "smohan ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/S-mohan/canvasTools/issues" 27 | }, 28 | "homepage": "https://github.com/S-mohan/canvasTools", 29 | "devDependencies": { 30 | "autoprefixer": "^7.1.1", 31 | "babel-core": "^6.24.1", 32 | "babel-loader": "^7.0.0", 33 | "babel-plugin-add-module-exports": "^0.2.1", 34 | "babel-preset-es2015": "^6.24.1", 35 | "css-loader": "^0.28.4", 36 | "extract-loader": "^0.1.0", 37 | "extract-text-webpack-plugin": "^2.1.0", 38 | "file-loader": "^0.11.1", 39 | "node-sass": "^4.5.3", 40 | "optimize-css-assets-webpack-plugin": "^1.3.2", 41 | "postcss-loader": "^2.0.5", 42 | "sass-loader": "^6.0.5", 43 | "style-loader": "^0.18.1", 44 | "webpack": "^2.6.1" 45 | }, 46 | "dependencies": {} 47 | } 48 | -------------------------------------------------------------------------------- /example/zhihu.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var $ = function(id) { 3 | return document.getElementById(id) 4 | } 5 | var $modal = $('modal') 6 | var $openModal = $('openModal') 7 | var $canvasToolBar = $('canvasToolBar') 8 | var $canvasWrap = $('canvasWrap') 9 | var $closeModal = $('closeModal') 10 | var $screenshot = $('screenshot') 11 | 12 | 13 | function parseHtml() { 14 | return $screenshot.outerHTML 15 | } 16 | var $canvas, canvasTools 17 | $openModal.addEventListener('click', function() { 18 | $modal.style.display = 'block' 19 | $canvas = document.createElement('canvas') 20 | var context = $canvas.getContext('2d') 21 | $canvasWrap.appendChild($canvas) 22 | 23 | rasterizeHTML.drawHTML(parseHtml(), $canvas, { 24 | executeJsTimeout: 3000 25 | }) 26 | .then(function(renderResult) { 27 | var width = $screenshot.offsetWidth 28 | var height = $screenshot.offsetHeight 29 | renderResult.image.width = width 30 | renderResult.image.height = height 31 | $canvas.width = width 32 | $canvas.height = height 33 | context.clearRect(0, 0, width, height) 34 | context.drawImage(renderResult.image, 0, 0, width ,height) 35 | $canvasWrap.style.maxHeight = height + 'px' 36 | setTimeout(function() { 37 | canvasTools = new CanvasTools($canvas, {container : $canvasToolBar}) 38 | }) 39 | }, function(error) { 40 | console.log(error) 41 | }) 42 | }) 43 | 44 | $closeModal.addEventListener('click', function() { 45 | $modal.style.display = 'none' 46 | $canvasWrap.style.maxHeight = 0 + 'px' 47 | $canvasWrap.removeChild($canvas) 48 | $canvas = null 49 | canvasTools.destory() 50 | }) 51 | })() -------------------------------------------------------------------------------- /example/zhihu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 36 | 37 | 38 | 39 |

CanvasTools.js

40 | 46 |
47 | 48 |
49 | 50 | 51 | 52 | 53 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const path = require('path') 3 | const env = require('yargs').argv.env; 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') 6 | const PackageJson = require('./package.json') 7 | 8 | const resolve = dir => path.join(__dirname, '.', dir) 9 | 10 | 11 | const banner = `CanvasTools v${PackageJson.version} 12 | (github)${PackageJson.homepage} 13 | (url) https://smohan.net/lab/canvastools 14 | (c) smohan 15 | license ${PackageJson.license}` 16 | 17 | 18 | let plugins = [], 19 | outputFile; 20 | 21 | if (env === 'build') { 22 | plugins = [ 23 | new ExtractTextPlugin('canvastools.min.css'), 24 | new webpack.optimize.UglifyJsPlugin({ 25 | minimize: true, 26 | comments: false, 27 | sourceMap: false, 28 | }), 29 | new OptimizeCssAssetsPlugin(), 30 | new webpack.BannerPlugin(banner), 31 | ]; 32 | outputFile = 'canvastools.min.js'; 33 | } else { 34 | plugins = [ 35 | new ExtractTextPlugin('canvastools.css'), 36 | new webpack.BannerPlugin(banner), 37 | ] 38 | outputFile = 'canvastools.js'; 39 | } 40 | 41 | module.exports = { 42 | entry: './src/js/main', 43 | devtool: env !== 'build' ? 'source-map' : false, 44 | output: { 45 | path: __dirname + '/dist', 46 | filename: outputFile, 47 | library: 'CanvasTools', 48 | libraryTarget: 'umd', 49 | umdNamedDefine: true 50 | }, 51 | module: { 52 | rules: [{ 53 | test: /\.js$/, 54 | loader: 'babel-loader', 55 | include: [resolve('src')] 56 | }, { 57 | test: /\.css$/, 58 | use: ExtractTextPlugin.extract(['css-loader', 'postcss-loader']) 59 | }, { 60 | test: /\.scss$/, 61 | use: ExtractTextPlugin.extract(['css-loader', 'sass-loader', 'postcss-loader']) 62 | }] 63 | }, 64 | resolve: { 65 | modules: [resolve('src')], 66 | extensions: ['.json', '.js'], 67 | alias: { 68 | '@': resolve('src') 69 | } 70 | }, 71 | plugins: plugins 72 | } -------------------------------------------------------------------------------- /example/demo.css: -------------------------------------------------------------------------------- 1 | @charset 'utf-8'; 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | } 8 | html, body { 9 | height: 100% 10 | } 11 | body { 12 | font-family: "Pingfang SC", "Helvetica Neue", Helvetica, "Hiragino Sans GB", "Hiragino Sans GB W3", "Microsoft YaHei UI", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif; 13 | font-size: 14px; 14 | line-height: 1.5; 15 | color: #515151; 16 | white-space: normal; 17 | background-color: #f0f0f0; 18 | user-select: none; 19 | } 20 | .link-tab { 21 | display: inline-block; 22 | vertical-align: middle; 23 | position: relative; 24 | font-size: 0; 25 | user-select: none; 26 | } 27 | .link-tab a { 28 | display: inline-block; 29 | vertical-align: middle; 30 | font-size: 14px; 31 | border: .0625rem solid #d3d3d3; 32 | white-space: nowrap; 33 | text-align: center; 34 | vertical-align: middle; 35 | cursor: pointer; 36 | outline: 0; 37 | font-size: .875rem; 38 | line-height: 1.4286; 39 | padding: .375rem 1rem; 40 | background-color: #fff; 41 | color: #515151; 42 | text-decoration: none; 43 | } 44 | .link-tab a.active { 45 | border-color: #5cb85c; 46 | background-color: #5cb85c; 47 | color: #fff; 48 | } 49 | .project-name { 50 | padding: 2rem 2rem 1rem 2rem; 51 | text-align: center; 52 | font-size: 3rem; 53 | font-weight: bold; 54 | color: #111; 55 | } 56 | .link-group { 57 | text-align: center; 58 | padding-bottom: 2rem 59 | } 60 | .modal, .modal > .overlap { 61 | position: fixed; 62 | width: 100%; 63 | height: 100%; 64 | top: 0; 65 | left: 0; 66 | } 67 | .modal { 68 | z-index: 100; 69 | user-select: none; 70 | text-align: center; 71 | } 72 | .modal > .overlap { 73 | background-color: rgba(0, 0, 0, .5); 74 | } 75 | .modal .modal-content { 76 | display: inline-block; 77 | margin-top: 5%; 78 | padding: 1rem; 79 | background-color: #fff; 80 | position: relative; 81 | z-index: 2; 82 | width: 580px; 83 | overflow-y: auto; 84 | } 85 | .modal-content .canvas-wrap { 86 | overflow: hidden; 87 | position: relative; 88 | transition: max-height .05s ease; 89 | min-height: 220px; 90 | background-color: #f6f7f9; 91 | border: 1px solid #ddd; 92 | } 93 | .modal-content .canvas-wrap canvas { 94 | display: block; 95 | max-width: 100%; 96 | } 97 | .modal-content .canvas-toolbar { 98 | position: relative; 99 | z-index: 10; 100 | margin-bottom: 10px; 101 | } 102 | .modal-content .modal-close { 103 | margin: 1rem 0 0 0; 104 | display: inline-block; 105 | padding: .5rem 1rem; 106 | border: 1px solid #ccc; 107 | cursor: pointer; 108 | background-color: #fff 109 | } 110 | -------------------------------------------------------------------------------- /example/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 59 | 60 | 61 | 62 |

CanvasTools.js

63 | 69 | 70 |
71 |
72 |

使用图片填充的画布

73 | 74 |
75 |
76 |
77 |

使用渐变填充的画布

78 | 79 |
80 |
81 |
82 |

工具栏悬浮

83 | 84 |
85 |
86 |
87 | 88 | 89 | 90 | 132 | 133 | -------------------------------------------------------------------------------- /src/js/template.js: -------------------------------------------------------------------------------- 1 | const ButtonsMap = { 2 | rect: { 3 | panel: 'stroke', 4 | name: '矩形工具' 5 | }, 6 | ellipse: { 7 | panel: 'stroke', 8 | name: '椭圆工具' 9 | }, 10 | brush: { 11 | panel: 'stroke', 12 | name: '画笔工具' 13 | }, 14 | arrow: { 15 | panel: 'stroke', 16 | name: '箭头工具' 17 | }, 18 | mosaic: { 19 | panel: 'mosaic', 20 | name: '马赛克工具' 21 | }, 22 | font: { 23 | panel: 'font', 24 | name: '文字工具' 25 | }, 26 | rubber: { 27 | name: '橡皮擦' 28 | }, 29 | undo: { 30 | name: '撤销操作' 31 | }, 32 | save: { 33 | name: '保存' 34 | } 35 | } 36 | 37 | 38 | //可用颜色 39 | const ColorList = ['#000000', '#808080', '#800000', '#f7883a', '#308430', '#385ad3', '#800080', '#009999', '#ffffff', '#c0c0c0', '#fb3838', '#ffff00', '#99cc00', '#3894e4', '#f31bf3', '#16dcdc'] 40 | 41 | //可选择字号,Chrome不支持小于12号的字体 42 | const FontSize = [12, 14, 16, 18, 20, 22] 43 | 44 | //画笔大小 45 | const StrokeWidth = [2, 4, 6] 46 | 47 | 48 | /** 49 | * 获取buttons模版 50 | * @param {Array} buttons [可用按钮] 51 | * @return {String} 52 | */ 53 | const getButtons = (buttons = []) => { 54 | let html = [] 55 | const useButton = btn => { 56 | return buttons && !!~buttons.indexOf(btn) 57 | } 58 | for (let key in ButtonsMap) { 59 | if (useButton(key)) { 60 | let btn = ButtonsMap[key] 61 | html.push(`
62 | 63 |
`) 64 | } 65 | } 66 | return html.join('') 67 | } 68 | 69 | 70 | /** 71 | * 获取颜色面板 72 | * @param {String} color [当前选中色] 73 | * @return {String} 74 | */ 75 | const getColorPanel = (color = '#fb3838') => { 76 | let html = '' 77 | html += '
' 78 | html += `` 79 | html += '
' 80 | 81 | let items1 = [], 82 | items2 = [] 83 | for (let i = 0; i < 16; i++) { 84 | let item = `
  • ` 85 | if (i < 8) { 86 | items1.push(item) 87 | } else { 88 | items2.push(item) 89 | } 90 | } 91 | 92 | html += `
      ${items1.join('')}
    ` 93 | html += `
      ${items2.join('')}
    ` 94 | 95 | html += '
    ' 96 | html += '
    ' 97 | 98 | return html 99 | } 100 | 101 | 102 | /** 103 | * 获取画笔大小面板 104 | * @param {Number} stroke [当前画笔大小] 105 | * @return {String} 106 | */ 107 | const getStrokePanel = (stroke = 2) => { 108 | let html = '
    ' 109 | for (let i = 0, len = StrokeWidth.length; i < len; i++) { 110 | let size = StrokeWidth[i] 111 | let classes = ['stroke', 'js-stroke-width'] 112 | size === stroke && classes.push('active') 113 | html += `` 114 | } 115 | html += '
    ' 116 | return html 117 | } 118 | 119 | 120 | /** 121 | * 获取字号选择器 122 | * @param {Number} fontSize [默认字号] 123 | * @return {String} 124 | */ 125 | const getFontPanel = (fontSize = 12) => { 126 | let html = '' 133 | return html 134 | } 135 | 136 | 137 | /** 138 | * 获取模糊度模版 139 | * @param {Number} ambiguite [默认模糊度 0 - 1] 140 | * @return {String} 141 | */ 142 | const getAmbiguity = (ambiguite = .5) => `` 143 | 144 | export default { 145 | getButtons, 146 | getColorPanel, 147 | getStrokePanel, 148 | getFontPanel, 149 | getAmbiguity, 150 | } -------------------------------------------------------------------------------- /src/js/utils.js: -------------------------------------------------------------------------------- 1 | const ArrayProto = Array.prototype 2 | 3 | const SelectorRegs = { 4 | id: /^#([\w-]+)$/, 5 | className: /^\.([\w-]+)$/, 6 | tagName: /^[\w-]+$/ 7 | } 8 | 9 | 10 | const isObject = obj => obj !== null && typeof obj === 'object' 11 | 12 | 13 | const isPlainObject = obj => Object.prototype.toString.call(obj) === '[object Object]' 14 | 15 | 16 | 17 | /** 18 | * 获取元素对象集合 19 | * @param {String} selector [选择器] 20 | * @param {HTMLElement} context [上下文对象] 21 | * @return {Array} [元素节点集合] 22 | */ 23 | const $ = (selector = '*', context = document) => { 24 | if (typeof selector === "string") { 25 | selector = selector.trim() 26 | let dom = []; 27 | if (SelectorRegs.id.test(selector)) { 28 | dom = document.getElementById(RegExp.$1) 29 | dom = dom ? [dom] : [] 30 | } else if (SelectorRegs.className.test(selector)) { 31 | dom = context.getElementsByClassName(RegExp.$1) 32 | } else if (SelectorRegs.tagName.test(selector)) { 33 | dom = context.getElementsByTagName(selector) 34 | } else { 35 | dom = context.querySelectorAll(selector) 36 | } 37 | return ArrayProto.slice.call(dom) 38 | } 39 | return [] 40 | } 41 | 42 | 43 | /** 44 | * 对象遍历 45 | * @param {Object | Array} object [对象源] 46 | * @param {Function} callback [回调] 47 | * @return 48 | */ 49 | const each = (object, callback) => { 50 | if (typeof object === "object" && typeof callback === "function") { 51 | if (Array.isArray(object)) { 52 | for (let i = 0, len = object.length; i < len; i++) { 53 | if (callback.call(object[i], i, object[i]) === false) { 54 | break 55 | } 56 | } 57 | } else if ('length' in object && typeof object.length === "number") { //这地方不太严谨,谨慎使用 58 | for (let k in object) { 59 | if (callback.call(object[k], k, object[k]) === false) { 60 | break 61 | } 62 | } 63 | } 64 | } 65 | } 66 | 67 | 68 | /** 69 | * 事件绑定,支持代理 70 | * @param {HTMLElement} element [DOM元素] 71 | * @param {String} eventType [事件类型] 72 | * @param {String} selector [选择器] 73 | * @param {Function} callback [回调] 74 | * @return 75 | */ 76 | const bind = (element, eventType, selector, callback) => { 77 | let sel, handler; 78 | if (typeof selector === "function") { 79 | handler = selector 80 | } else if (typeof selector === "string" && typeof callback === "function") { 81 | sel = selector 82 | } else { 83 | return 84 | } 85 | if (sel) { //事件代理 86 | handler = function(e) { 87 | const nodes = $(sel, element) 88 | let matched = false 89 | for (let i = 0, len = nodes.length; i < len; i++) { 90 | const node = nodes[i] 91 | if (node === e.target || node.contains(e.target)) { 92 | matched = node 93 | break 94 | } 95 | } 96 | if (matched) { 97 | callback.apply(matched, ArrayProto.slice.call(arguments)) 98 | } 99 | } 100 | } 101 | 102 | element.addEventListener(eventType, handler, false) 103 | } 104 | 105 | 106 | /** 107 | * 事件解绑 108 | * @param {HTMLElement} element [DOM元素] 109 | * @param {String} eventType [事件类型] 110 | * @param {Function} callback [回调] 111 | * @return 112 | */ 113 | const unbind = (element, eventType, callback) => element.removeEventListener(eventType, callback, false) 114 | 115 | 116 | /** 117 | * 获取指定元素样式 118 | * @param {HTMLElement} element 119 | * @return {Object} 120 | */ 121 | const getComputedStyles = element => element.ownerDocument.defaultView.getComputedStyle(element, null) 122 | 123 | 124 | /** 125 | * 对象深度拷贝 126 | * @param {Object} out 127 | * @return {Object} 128 | */ 129 | const extend = function(out = {}) { 130 | for (let i = 1, len = arguments.length; i < len; i++) { 131 | let obj = arguments[i] 132 | if (!obj || !Object.keys(obj).length) 133 | continue 134 | for (let key in obj) { 135 | if (obj.hasOwnProperty(key)) { 136 | if (isPlainObject(obj[key])) 137 | out[key] = extend(out[key], obj[key]) 138 | else 139 | out[key] = obj[key] 140 | } 141 | } 142 | } 143 | return out 144 | } 145 | 146 | 147 | const $on = (elements, eventType, selector, callback) => { 148 | if (!Array.isArray(elements)) { 149 | elements = [elements] 150 | } 151 | each(elements, (index, element) => bind(element, eventType, selector, callback)) 152 | } 153 | 154 | const $off = (elements, eventType, callback) => { 155 | if (!Array.isArray(elements)) { 156 | elements = [elements] 157 | } 158 | each(elements, (index, element) => unbind(element, eventType, callback)) 159 | } 160 | 161 | const classList = (elements, type = 'add', classes = '') => { 162 | if (!Array.isArray(elements)) { 163 | elements = [elements] 164 | } 165 | each(elements, (index, element) => element.classList[type](classes)) 166 | } 167 | 168 | 169 | export default { 170 | $, 171 | each, 172 | bind, 173 | extend, 174 | unbind, 175 | getComputedStyles, 176 | classList, 177 | $on, 178 | $off, 179 | } -------------------------------------------------------------------------------- /src/scss/canvastools.scss: -------------------------------------------------------------------------------- 1 | .canvas-tools, .canvas-tools * { 2 | box-sizing: border-box; 3 | } 4 | @font-face { 5 | font-family: "CanvasTools"; 6 | src: url(data:application/font-woff;base64,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) format('woff'); 7 | } 8 | [class^="canvas-tools-icon__"], [class*=" canvas-tools-icon__"] { 9 | font-family: "CanvasTools" !important; 10 | font-size: 16px; 11 | font-style: normal; 12 | line-height: 1; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | } 16 | .canvas-tools-icon__font:before { 17 | content: "\e620"; 18 | } 19 | .canvas-tools-icon__save:before { 20 | content: "\e60b"; 21 | } 22 | .canvas-tools-icon__undo:before { 23 | content: "\e631"; 24 | } 25 | .canvas-tools-icon__arrow:before { 26 | content: "\e50a"; 27 | } 28 | .canvas-tools-icon__rubber:before { 29 | content: "\e6b8"; 30 | } 31 | .canvas-tools-icon__rect:before { 32 | content: "\e619"; 33 | } 34 | .canvas-tools-icon__ellipse:before { 35 | content: "\e61a"; 36 | } 37 | .canvas-tools-icon__mosaic:before { 38 | content: "\e622"; 39 | } 40 | .canvas-tools-icon__brush:before { 41 | content: "\e69a"; 42 | } 43 | .canvas-tools { 44 | display: inline-block; 45 | border: 1px solid #dcdcdc; 46 | height: 2rem; 47 | position: relative; 48 | font-size: 0; 49 | padding: 3px 5px; 50 | user-select: none; 51 | background: #fff; 52 | box-shadow: 0 1px 3px rgba(#000,.075), 0 1px 4px rgba(#000,.075); 53 | [class^="canvas-tools-icon__"], [class*=" canvas-tools-icon__"] { 54 | font-size: 1.125rem; 55 | position: relative; 56 | } 57 | .canvas-tools-btn { 58 | display: inline-block; 59 | vertical-align: middle; 60 | height: 1.5rem; 61 | padding: .15rem .2rem; 62 | border: 1px solid transparent; 63 | transition: all .2s; 64 | margin-right: .5rem; 65 | position: relative; 66 | text-align: center; 67 | 68 | .btn-toggle { 69 | display: flex; 70 | height: 100%; 71 | text-decoration: none; 72 | outline: none; 73 | flex-flow: cloumn; 74 | align-items: center; 75 | justify-content: center; 76 | cursor: pointer; 77 | } 78 | &:hover, &.active { 79 | background-image: linear-gradient(to bottom, #dcdcdc, #fff); 80 | background-color: #efefef; 81 | border-color: #dedede; 82 | } 83 | &.active { 84 | border-color: #d2d5d7; 85 | } 86 | .canvas-tools-icon__font { 87 | position: relative; 88 | top: -1px; 89 | } 90 | .canvas-tools-icon__font, .canvas-tools-icon__arrow { 91 | color: #2f96e0 92 | } 93 | .canvas-tools-icon__save { 94 | color: #7492d7; 95 | top: 1px; 96 | } 97 | .canvas-tools-icon__undo{ 98 | color: #68af45; 99 | } 100 | .canvas-tools-icon__rect, .canvas-tools-icon__ellipse { 101 | color: #8eb1c8; 102 | } 103 | .canvas-tools-icon__rubber { 104 | color: #429fe2 105 | } 106 | .canvas-tools-icon__mosaic { 107 | color: #a6c2d8; 108 | } 109 | .canvas-tools-icon__brush { 110 | color: #68af45; 111 | } 112 | } 113 | } 114 | .canvas-tools .canvas-tools__panel { 115 | font-size: 0; 116 | position: absolute; 117 | top: 100%; 118 | margin-top: 5px; 119 | left: 0; 120 | background-color: rgba(#fff, .95); 121 | border: 1px solid #d7dee2; 122 | cursor: default; 123 | width: auto; 124 | padding: 4px 10px; 125 | text-align: left; 126 | display: none; 127 | white-space: nowrap!important; 128 | box-shadow: 0 1px 3px rgba(#000,.05), 0 1px 4px rgba(#000,.05); 129 | > .font-select { 130 | display: inline-block; 131 | vertical-align: middle; 132 | padding: 3px 5px; 133 | border : 1px solid #d2d5d7; 134 | outline: none; 135 | } 136 | > .strokes { 137 | display: inline-block; 138 | vertical-align: middle; 139 | padding-right: 4px; 140 | border-right: 1px solid #d2d5d7; 141 | 142 | > .stroke { 143 | width: 24px; 144 | height: 24px; 145 | display: inline-block; 146 | vertical-align: middle; 147 | border: 1px solid transparent; 148 | text-align: center; 149 | cursor: pointer; 150 | transition: all .2s; 151 | margin-right: 2px; 152 | 153 | &:last-child { 154 | margin-right: 0; 155 | } 156 | &:hover, &.active { 157 | background-color: #dee1e8; 158 | border-color: #d7dee2; 159 | } 160 | &.active { 161 | border-color: #d2d5d7; 162 | } 163 | &:before, > i { 164 | display: inline-block; 165 | vertical-align: middle; 166 | } 167 | &:before { 168 | content: ''; 169 | height: 100%; 170 | } 171 | i { 172 | border-radius: 50%; 173 | background-color: #2f8ddd; 174 | } 175 | } 176 | } 177 | > .colors { 178 | display: inline-block; 179 | vertical-align: middle; 180 | margin-left: 8px; 181 | 182 | > .color-selected, > .color-list { 183 | display: inline-block; 184 | vertical-align: middle; 185 | } 186 | > .color-selected { 187 | width: 28px; 188 | height: 28px; 189 | border: 1px solid #d2d5d7; 190 | padding: 2px; 191 | background-color: #fff; 192 | > i { 193 | display: block; 194 | width: 100%; 195 | height: 100%; 196 | transition: all .2s; 197 | } 198 | } 199 | > .color-list { 200 | margin-left: 4px; 201 | height: 28px; 202 | 203 | > ul { 204 | list-style-type: none; 205 | margin: 0; 206 | padding: 0; 207 | 208 | > li { 209 | display: inline-block; 210 | vertical-align: middle; 211 | width: 13px; 212 | height: 13px; 213 | border: 1px solid #d2d5d7; 214 | margin-right: 2px; 215 | 216 | &:last-child { 217 | margin-right: 0; 218 | } 219 | &:hover { 220 | transform: scale(1.2); 221 | border-color: #fff; 222 | } 223 | } 224 | &:last-child { 225 | margin-top: 2px; 226 | } 227 | } 228 | } 229 | } 230 | 231 | > .ambiguite-range { 232 | font-size: 12px; 233 | display: inline-block; 234 | vertical-align: middle; 235 | margin-left: 8px; 236 | span, input { 237 | display: inline-block; 238 | vertical-align: middle; 239 | } 240 | input { 241 | width: 100px; 242 | margin-left: 5px; 243 | } 244 | } 245 | } 246 | 247 | $brush : url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABGdBTUEAALGPC/xhBQAAARpJREFUOBGdkr1KA0EUhTcxEkSQQIpAihQWPoedD2BnIVsZQiBVEkiR7SwtBDsJqXwBLQwEgoWNjY2FjYUQ38Ei5Oc7sBeGLTKze+Djzsyecxh2N4ryq0zkHpYQQ2H1SW5TNsxukaZzQsewACvTvIBg3eFUaABHMEv3OruBIN3icm8wZl+FZ3iBQ/AqweGW2FrlKlChV0McFsxOvaegm/T2lLzzTC/dqzaO7A1s/8GzE28Dhhj0f1jQnZ+c18CrKxxrcMO2/uK87m3AcAkrsKA7vzlvQJAmuNywrX84bwY1YBrBATyCFWj+QguCJKNCU1DZQ7r/Y55CsK5x2i2eWFcggTPIJd3Eiv5Zd3KlU3OJOQd9lVd4A5Xl1g4YG2GGhwRfegAAAABJRU5ErkJggg==)!default; 248 | 249 | $mosaic : url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGM0NFMkEyQTRCOTcxMUU3OTc2Mzg4ODUyQTFDQUVGNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGM0NFMkEyQjRCOTcxMUU3OTc2Mzg4ODUyQTFDQUVGNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkYzQ0UyQTI4NEI5NzExRTc5NzYzODg4NTJBMUNBRUY3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkYzQ0UyQTI5NEI5NzExRTc5NzYzODg4NTJBMUNBRUY3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+BtIaIAAAA8pJREFUeNrEl99LU2Ecxs9++IOcmdZkieg2CdmchWgWSCBpFIRk3kldCKLhdIpo/hMVQkujRNlFoHeVdqGQIlRQpijlDxRsmpJjymhtlem2eh45iyUSJ93WFx7PPBvn87zve855n69Mr9cLEisBKoGKoBwoAzosfvcF+gjNQK+gYeirlIvKJBjQQg1QuUKhSJTJZHIc5TKx+IOfYvn9/gAOARw9OP0Eugct7ddAHNQKxg25XB4fExOjSE1NXczJyZkuLCz8kJeXt67T6XZGabfbEyYnJ9VjY2P6mZkZk9PpzNre3vYHAoFNGHqIn9yCfvyLAY66CyM9GRsbq8zOzn5TV1c3VFpa6pQyrcPDw+rOzs5L8/PzZ7a2tnyYkXc4XbPXbOxlIBd6pFQq05KTk11NTU22ysrKJWEf1dvbq21vb692u91JPp/vE05dh97/zQBHPoDpPp6RkbHQ09PTlZ6evikcoGZnZ1X19fW1a2trOizLGk6Vhc5EqIF46BlGfiotLc0+MDBwV6VS+YUwlNfrVZSXlzesrKxkYSa4HJehnYHJQ37XijXPTUpKcnd0dHSFC87itWw224OUlBQXGCaygt8FDWhxt9fyhmtubu42Go1eIczFpWxsbLSRQZa43L8NNPBR492+3xtOSvHaYLwmC/9aggZUfMnwObdYLINChAuMIbLw8QrZNHCebziNRrNYXFy8HmkDZJBFJtk0cI6vV4PBMC1Eqcgik2z+MfDdnp+fb4+WAbzG7WSSLRefAFlBQYEzWgawlzjFjUxLA9zhZJmZmd+iZYCbmGggUS7856IBD/fy5eXlQ9GCcvsmk2waWOI/4+PjqdEyQJZoYIkG5phkJiYmdNEyQBaZZNPAC8aoubk5U7QMkEUm2TQwwgzncDiyRkdH1ZGGk0EWmNzwRmiAH54yw1mt1ouRNkAGWWJo9QYfQysDJDLcWcaoSMH7+voymRPJEhOzoEDu4/Ezcz/W5fTU1JSxqKjorVqt3gonfHV1Nb6lpaUB6SgOBu7jVP/uRHQb6zKNAHkEGa6GMSpccF6rqqqq1uVyHSUDp+7sTkSCmNGqkdkcCJD6iooKM45xB4VjylVlZWVNmIETvDYZ0Pfg98ElCBaX4iWm6ILH49H29/cXIM/ZTSaTe79r3tbWZtnY2NCIsfwatCC1MelmgAw2JmazebCkpERSYBEbk4u8qcXGZFocuaTGJLQ1u8kAuUdrtog9fWNXa3YMrVlWuFqzP3ZPqB66ihlRSWxOaeyx+KjZD9odh7bnpRLb8+dS2/NfAgwAg5rkol00QQkAAAAASUVORK5CYII=)!default; 250 | 251 | 252 | .canvas-cursor__brush { 253 | cursor: $brush 0 17, default; 254 | cursor: -webkit-image-set($brush 1x, $brush 2x) 0 17, default; 255 | } 256 | 257 | .canvas-cursor__mosaic { 258 | cursor: $mosaic 0 17, default; 259 | cursor: -webkit-image-set($mosaic 1x, $mosaic 2x) 0 17, default; 260 | } 261 | 262 | .canvas-cursor__crosshair { 263 | cursor: crosshair; 264 | } 265 | 266 | .canvas-cursor__font { 267 | cursor: text; 268 | } 269 | 270 | .canvas-cursor__default { 271 | cursor: default; 272 | } 273 | 274 | .canvas-tools_text_helper { 275 | display: none; 276 | position: absolute; 277 | border: 1px dashed #000; 278 | padding: 2px; 279 | outline: none; 280 | min-width: 16px; 281 | min-height: 16px; 282 | line-height: 16px; 283 | resize : none; 284 | overflow: hidden; 285 | background-color: transparent; 286 | } -------------------------------------------------------------------------------- /dist/canvastools.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * CanvasTools v1.0.0 3 | * (github)https://github.com/S-mohan/canvasTools 4 | * (url) https://smohan.net/lab/canvastools 5 | * (c) smohan 6 | * license MIT 7 | */ 8 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("CanvasTools",[],e):"object"==typeof exports?exports.CanvasTools=e():t.CanvasTools=e()}(this,function(){return function(t){function e(o){if(n[o])return n[o].exports;var a=n[o]={i:o,l:!1,exports:{}};return t[o].call(a.exports,a,a.exports,e),a.l=!0,a.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,o){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:o})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=4)}([function(t,e){},function(t,e,n){"use strict";window.Element&&!Element.prototype.closest&&(Element.prototype.closest=function(t){var e,n=(this.document||this.ownerDocument).querySelectorAll(t),o=this;do{for(e=n.length;--e>=0&&n.item(e)!==o;);}while(e<0&&(o=o.parentElement));return o})},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var o={rect:{panel:"stroke",name:"矩形工具"},ellipse:{panel:"stroke",name:"椭圆工具"},brush:{panel:"stroke",name:"画笔工具"},arrow:{panel:"stroke",name:"箭头工具"},mosaic:{panel:"mosaic",name:"马赛克工具"},font:{panel:"font",name:"文字工具"},rubber:{name:"橡皮擦"},undo:{name:"撤销操作"},save:{name:"保存"}},a=["#000000","#808080","#800000","#f7883a","#308430","#385ad3","#800080","#009999","#ffffff","#c0c0c0","#fb3838","#ffff00","#99cc00","#3894e4","#f31bf3","#16dcdc"],i=[12,14,16,18,20,22],s=[2,4,6],r=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],e=[];for(var n in o)if(function(e){return t&&!!~t.indexOf(e)}(n)){var a=o[n];e.push('
    \n\t\t\t\n\t\t\t
    ')}return e.join("")},l=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"#fb3838",e="";e+='
    ',e+='',e+='
    ';for(var n=[],o=[],i=0;i<16;i++){var s='
  • ';i<8?n.push(s):o.push(s)}return e+="
      "+n.join("")+"
    ",e+="
      "+o.join("")+"
    ",e+="
    ",e+="
    "},c=function(){for(var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:2,e='
    ',n=0,o=s.length;n'}return e+="
    "},u=function(){for(var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:12,e='"},f=function(){return''};e.default={getButtons:r,getColorPanel:l,getStrokePanel:c,getFontPanel:u,getAmbiguity:f},t.exports=e.default},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},a=Array.prototype,i={id:/^#([\w-]+)$/,className:/^\.([\w-]+)$/,tagName:/^[\w-]+$/},s=function(t){return"[object Object]"===Object.prototype.toString.call(t)},r=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"*",e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:document;if("string"==typeof t){t=t.trim();var n=[];return i.id.test(t)?(n=document.getElementById(RegExp.$1),n=n?[n]:[]):n=i.className.test(t)?e.getElementsByClassName(RegExp.$1):i.tagName.test(t)?e.getElementsByTagName(t):e.querySelectorAll(t),a.slice.call(n)}return[]},l=function(t,e){if("object"===(void 0===t?"undefined":o(t))&&"function"==typeof e)if(Array.isArray(t))for(var n=0,a=t.length;n0&&void 0!==arguments[0]?arguments[0]:{},n=1,o=arguments.length;n1&&void 0!==arguments[1]?arguments[1]:"add",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";Array.isArray(t)||(t=[t]),l(t,function(t,o){return o.classList[e](n)})};e.default={$:r,each:l,bind:c,extend:d,unbind:u,getComputedStyles:f,classList:g,$on:h,$off:p},t.exports=e.default},function(t,e,n){"use strict";function o(t){return t&&t.__esModule?t:{default:t}}function a(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(){var t=this,e=this.canvas,n=this.context,o=this.$el,a=this.state,i=(this.config,this.rect),h=this._handles,p=this.history,v=g.default.$(".js-btn",o),m=g.default.$(".js-panel__font",o)[0],b=g.default.$(".js-panel__stroke",o)[0],w=g.default.$(".js-panel__mosaic",o)[0],x=g.default.$(".js-color-selected",o),k=g.default.$(".js-color",o),$=g.default.$(".js-stroke-width",o),T=g.default.$(".js-font-size",o),j=g.default.$(".js-mosaic-ambiguity",o);h.btnEmit=function(e){var o=this;e.stopPropagation(),"font"===a.drawType&&c.call(t,e);var s=this.getAttribute("data-panel"),r=this.getAttribute("data-value");if(g.default.each(v,function(t,e){e!==o&&g.default.classList(e,"remove","active")}),~y.indexOf(r)&&(a.drawType=r),s){g.default.classList(this,"toggle","active");var l=/active/.test(this.className),u=l?"block":"none";"stroke"===s?(m.style.display="none",w.style.display="none",b.style.display=u):"font"===s?(m.style.display=u,b.style.display="none",w.style.display="none"):"mosaic"===s&&(w.style.display=u,m.style.display="none",b.style.display="none")}if("save"===r)return void O.call(t);"undo"===r&&p.length>1&&(p.pop(),n.putImageData(p[p.length-1],0,0,0,0,i.width,i.height)),f.call(t)},h.toggleColor=function(t){var e=this.getAttribute("data-value");a.strokeColor=e,g.default.each(x,function(t,n){return n.style.background=e})},h.toggleStrokeWidth=function(t){a.strokeWidth=Number(this.getAttribute("data-value")),g.default.each($,function(t,e){var n=Number(e.getAttribute("data-value")),o=n===a.strokeWidth?"add":"remove";g.default.classList(e,o,"active")})},h.toggleFontSize=function(t){a.fontSize=Number(this.value)};var _=void 0;h.onMouseDown=function(e){if(!1!=!!~y.indexOf(a.drawType)&&"font"!==a.drawType){switch(_=A(e,i),a.lastImageData=n.getImageData(0,0,i.width,i.height),n.lineCap="round",n.lineJoin="round",n.shadowBlur=0,n.strokeStyle=a.strokeColor,n.lineWidth=a.strokeWidth,a.drawType){case"rect":s.call(t,e,_);break;case"ellipse":r.call(t,e,_);break;case"mosaic":u.call(t,e);break;case"brush":default:l.call(t,e,_)}g.default.$on(document,"mousemove",h.onMouseMove),g.default.$on(document,"mouseup",h.onMouseUp)}},h.onMouseMove=function(e){if(!1!=!!~y.indexOf(a.drawType)&&"font"!==a.drawType)switch(a.drawType){case"rect":s.call(t,e,_);break;case"ellipse":r.call(t,e,_);break;case"mosaic":u.call(t,e);break;case"brush":default:l.call(t,e,null)}},h.onMouseUp=function(e){g.default.$off(document,"mousemove",h.onMouseMove),g.default.$off(document,"mouseup",h.onMouseUp),~y.indexOf(a.drawType)&&"font"!==a.drawType&&d.call(t)},h.insertTextHelper=function(e){if(e.stopPropagation(),"font"===a.drawType){if(a.isEntry)return void c.call(t,e);M(e,a,i),a.isEntry=!0}},h.removeTextHelper=function(e){"font"!==a.drawType?P():e.target.closest("#canvas-tools-input")||c.call(t,e)},h.resize=function(n){var o=e.getBoundingClientRect();i.width=e.width,i.height=e.height,i.offsetWidth=e.offsetWidth,i.offsetHeight=e.offsetHeight,i.top=o.top,i.left=o.left,"font"===a.drawType&&a.isEntry&&c.call(t,n)},h.toggleAmbiguity=function(t){a.ambiguity=this.value,console.log(a)},g.default.$on(v,"click",h.btnEmit),g.default.$on(k,"click",h.toggleColor),g.default.$on($,"click",h.toggleStrokeWidth),g.default.$on(T,"change",h.toggleFontSize),g.default.$on(j,"change",h.toggleAmbiguity),g.default.$on(e,"mousedown",h.onMouseDown),g.default.$on(e,"click",h.insertTextHelper),g.default.$on(document,"click",h.removeTextHelper),window.addEventListener("resize",h.resize)}function s(t,e){var n=this.context,o=this.rect,a=this.state,i=A(t,o),s=i.x-e.x,r=i.y-e.y;n.clearRect(0,0,o.width,o.height),n.putImageData(a.lastImageData,0,0,0,0,o.width,o.height),n.save(),n.beginPath(),n.strokeRect(e.x,e.y,s,r),n.restore(),n.closePath()}function r(t,e){var n=this.context,o=this.rect,a=this.state,i=A(t,o),s=(i.x-e.x)/2*1,r=(i.y-e.y)/2*1,l=e.x/s+1,c=e.y/r+1;n.clearRect(0,0,o.width,o.height),n.putImageData(a.lastImageData,0,0,0,0,o.width,o.height),n.save(),n.beginPath(),n.scale(s,r),n.arc(l,c,1,0,2*Math.PI),n.restore(),n.closePath(),n.stroke()}function l(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,n=this.context,o=this.rect;if(e)n.beginPath(),n.moveTo(e.x,e.y);else{var a=A(t,o);n.lineTo(a.x,a.y),n.stroke()}}function c(t){var e=document.getElementById(k);if(!e)return void(this.state.isEntry=!1);var n=e.textContent.trim(),o=n.length;if(!n||!o)return this.state.isEntry=!1,void P();var a=g.default.getComputedStyles(e),i=this.state.fontSize||$,s=2*x,r=this.context,l=parseFloat(a.left)-this.rect.left+s-2,c=parseFloat(a.top)-this.rect.top+i,u=0,f=0;r.beginPath(),r.save(),r.fillStyle=this.state.strokeColor,r.font=this.state.fontSize+"px "+T;for(var h=0;hthis.rect.width-l&&(r.fillText(n.substring(f,h),l,c),c+=i,u=0,f=h),h==o-1&&r.fillText(n.substring(f,h+1),l,c)}r.restore(),r.closePath(),this.state.isEntry=!1,P(),d.call(this)}function u(t){for(var e=this.context,n=this.state,o=this.rect,a=A(t,o),i=3*n.strokeWidth,s=e.getImageData(a.x,a.y,i,i).data,r=0,l=0,c=0,u=0;u0&&void 0!==arguments[0]?arguments[0]:b,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:b,n=document.createElement("div");return n.className="canvas-tools__panel js-panel__stroke",n.style.cssText+="white-space: nowrap!important;",n.innerHTML=m.default.getStrokePanel(t)+m.default.getColorPanel(e),n},E=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:$,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:b,n=document.createElement("div");return n.className="canvas-tools__panel js-panel__font",n.style.cssText+="white-space: nowrap!important;",n.innerHTML=m.default.getFontPanel(t)+m.default.getColorPanel(e),n},C=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:b,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:j,n=document.createElement("div");return n.className="canvas-tools__panel js-panel__mosaic",n.style.cssText+="white-space: nowrap!important;",n.innerHTML=m.default.getStrokePanel(t)+m.default.getAmbiguity(e),n},S=function(){var t=document.getElementById(k);return t||(t=document.createElement("div"),t.setAttribute("contenteditable","plaintext-only"),t.setAttribute("spellcheck","false"),t.setAttribute("id",k),t.className=k,document.body.appendChild(t)),t.innerHTML="",t.style.cssText="display: block",t},M=function(t,e,n){var o=S(),a=e.fontSize||$,i=2*x+2,s=A(t,n),r=t.pageX,l=t.pageY,c=Math.floor(n.width-s.x)-i,u=Math.floor(n.height-s.y)-i;c<=a&&(r-=a+i-c,c=a),u<=a&&(l-=a+i-u,u=a),c>=n.width&&(c=n.width-s.x),u>=n.height&&(u=n.height-s.y);var f={"font-size":a+"px","line-height":a+"px","min-width":a+"px","min-height":a+"px","max-width":c+"px","max-height":u+"px","z-index":1990,"font-family":T,display:"block",position:"absolute",top:l+"px",left:r+"px",color:e.strokeColor,padding:x+"px",overflow:"hidden"},d="";for(var h in f)d+=h+":"+f[h]+";";return o.style.cssText=d,o.focus(),o},P=function(){var t=document.getElementById(k);t&&(t.innerHTML="",t.style.cssText="display: none")},A=function(t,e){var n=t.pageX-e.left,o=t.pageY-e.top;return n<=0&&(n=0),n>=e.width&&(n=e.width),o<=0&&(o=0),o>=e.height&&(o=e.height),{x:n,y:o}},H={container:document.body,buttons:["rect","ellipse","brush","font","mosaic","undo","save"]},N=document.createElementNS("http://www.w3.org/1999/xhtml","a"),z="download"in N,O=function(){var t="canvas_"+Date.now()+".png",e=this.canvas;if(z){var n=e.toDataURL("png");n=n.replace("image/png","image/octet-stream"),setTimeout(function(){N.href=n,N.download=t,N.dispatchEvent(new MouseEvent("click"))})}else"undefined"!=typeof navigator&&"function"==typeof e.msToBlob&&navigator.msSaveBlob?navigator.msSaveBlob(e.msToBlob(),t):console.log("您的浏览器不支持该操作")},B=function(){function t(e,n){if(a(this,t),!e||"function"!=typeof e.getContext)throw new Error("invalid canvas object");this.canvas=e,this.context=e.getContext("2d"),this.config=g.default.extend({},H,n||{}),this.history=[],this.state=Object.create(null),this.rect=Object.create(null),this._handles=Object.create(null),this.state.strokeWidth=w,this.state.fontSize=$,this.state.strokeColor=b,this.state.ambiguity=j,this.state.drawType="brush",this.state.isEntry=!1,this.rect.width=e.width,this.rect.height=e.height,this.rect.offsetWidth=e.offsetWidth,this.rect.offsetHeight=e.offsetHeight;var o=e.getBoundingClientRect();this.rect.top=o.top,this.rect.left=o.left,this.state.lastImageData=this.context.getImageData(0,0,this.rect.width,this.rect.height),d.call(this),f.call(this),this.render()}return h(t,[{key:"render",value:function(){var t=this.config,e=this.state,n=document.createElement("div");n.className="canvas-tools",n.innerHTML=m.default.getButtons(t.buttons),t.container.appendChild(n),this.$el=n,this.$el.appendChild(_(e.strokeWidth,e.strokeColor)),this.$el.appendChild(E(e.fontSize,e.strokeColor)),this.$el.appendChild(C(e.strokeWidth,e.ambiguity)),i.call(this)}},{key:"refresh",value:function(){}},{key:"destory",value:function(){var t=this.canvas,e=this.$el,n=this._handles,o=g.default.$(".js-btn",e),a=g.default.$(".js-color",e),i=g.default.$(".js-stroke-width",e),s=g.default.$(".js-font-size",e),r=g.default.$(".js-mosaic-ambiguity",e),l=document.getElementById(k);g.default.$off(o,"click",n.btnEmit),g.default.$off(a,"click",n.toggleColor),g.default.$off(i,"click",n.toggleStrokeWidth),g.default.$off(s,"change",n.toggleFontSize),g.default.$off(r,"change",n.toggleAmbiguity),g.default.$off(t,"mousedown",n.onMouseDown),g.default.$off(t,"click",n.insertTextHelper),g.default.$off(document,"click",n.removeTextHelper),window.removeEventListener("resize",n.resize),l&&l.parentNoed.removeChild(l),this.canvas=null,this.context=null,this.history.length=0,this.config.container.removeChild(this.$el)}}]),t}();e.default=B,t.exports=e.default}])}); -------------------------------------------------------------------------------- /dist/canvastools.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * CanvasTools v1.0.0 3 | * (github)https://github.com/S-mohan/canvasTools 4 | * (url) https://smohan.net/lab/canvastools 5 | * (c) smohan 6 | * license MIT 7 | */.canvas-tools,.canvas-tools *{box-sizing:border-box}@font-face{font-family:CanvasTools;src:url(data:application/font-woff;base64,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) format("woff")}[class*=" canvas-tools-icon__"],[class^=canvas-tools-icon__]{font-family:CanvasTools!important;font-size:16px;font-style:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.canvas-tools-icon__font:before{content:"\E620"}.canvas-tools-icon__save:before{content:"\E60B"}.canvas-tools-icon__undo:before{content:"\E631"}.canvas-tools-icon__arrow:before{content:"\E50A"}.canvas-tools-icon__rubber:before{content:"\E6B8"}.canvas-tools-icon__rect:before{content:"\E619"}.canvas-tools-icon__ellipse:before{content:"\E61A"}.canvas-tools-icon__mosaic:before{content:"\E622"}.canvas-tools-icon__brush:before{content:"\E69A"}.canvas-tools{display:inline-block;border:1px solid #dcdcdc;height:2rem;position:relative;font-size:0;padding:3px 5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.075),0 1px 4px rgba(0,0,0,.075)}.canvas-tools [class*=" canvas-tools-icon__"],.canvas-tools [class^=canvas-tools-icon__]{font-size:1.125rem;position:relative}.canvas-tools .canvas-tools-btn{display:inline-block;vertical-align:middle;height:1.5rem;padding:.15rem .2rem;border:1px solid transparent;transition:all .2s;margin-right:.5rem;position:relative;text-align:center}.canvas-tools .canvas-tools-btn .btn-toggle{display:-webkit-box;display:-ms-flexbox;display:flex;height:100%;text-decoration:none;outline:none;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-flow:cloumn;flex-flow:cloumn;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;cursor:pointer}.canvas-tools .canvas-tools-btn.active,.canvas-tools .canvas-tools-btn:hover{background-image:linear-gradient(180deg,#dcdcdc,#fff);background-color:#efefef;border-color:#dedede}.canvas-tools .canvas-tools-btn.active{border-color:#d2d5d7}.canvas-tools .canvas-tools-btn .canvas-tools-icon__font{position:relative;top:-1px}.canvas-tools .canvas-tools-btn .canvas-tools-icon__arrow,.canvas-tools .canvas-tools-btn .canvas-tools-icon__font{color:#2f96e0}.canvas-tools .canvas-tools-btn .canvas-tools-icon__save{color:#7492d7;top:1px}.canvas-tools .canvas-tools-btn .canvas-tools-icon__undo{color:#68af45}.canvas-tools .canvas-tools-btn .canvas-tools-icon__ellipse,.canvas-tools .canvas-tools-btn .canvas-tools-icon__rect{color:#8eb1c8}.canvas-tools .canvas-tools-btn .canvas-tools-icon__rubber{color:#429fe2}.canvas-tools .canvas-tools-btn .canvas-tools-icon__mosaic{color:#a6c2d8}.canvas-tools .canvas-tools-btn .canvas-tools-icon__brush{color:#68af45}.canvas-tools .canvas-tools__panel{font-size:0;position:absolute;top:100%;margin-top:5px;left:0;background-color:hsla(0,0%,100%,.95);border:1px solid #d7dee2;cursor:default;width:auto;padding:4px 10px;text-align:left;display:none;white-space:nowrap!important;box-shadow:0 1px 3px rgba(0,0,0,.05),0 1px 4px rgba(0,0,0,.05)}.canvas-tools .canvas-tools__panel>.font-select{display:inline-block;vertical-align:middle;padding:3px 5px;border:1px solid #d2d5d7;outline:none}.canvas-tools .canvas-tools__panel>.strokes{display:inline-block;vertical-align:middle;padding-right:4px;border-right:1px solid #d2d5d7}.canvas-tools .canvas-tools__panel>.strokes>.stroke{width:24px;height:24px;display:inline-block;vertical-align:middle;border:1px solid transparent;text-align:center;cursor:pointer;transition:all .2s;margin-right:2px}.canvas-tools .canvas-tools__panel>.strokes>.stroke:last-child{margin-right:0}.canvas-tools .canvas-tools__panel>.strokes>.stroke.active,.canvas-tools .canvas-tools__panel>.strokes>.stroke:hover{background-color:#dee1e8;border-color:#d7dee2}.canvas-tools .canvas-tools__panel>.strokes>.stroke.active{border-color:#d2d5d7}.canvas-tools .canvas-tools__panel>.strokes>.stroke:before,.canvas-tools .canvas-tools__panel>.strokes>.stroke>i{display:inline-block;vertical-align:middle}.canvas-tools .canvas-tools__panel>.strokes>.stroke:before{content:"";height:100%}.canvas-tools .canvas-tools__panel>.strokes>.stroke i{border-radius:50%;background-color:#2f8ddd}.canvas-tools .canvas-tools__panel>.colors{display:inline-block;vertical-align:middle;margin-left:8px}.canvas-tools .canvas-tools__panel>.colors>.color-list,.canvas-tools .canvas-tools__panel>.colors>.color-selected{display:inline-block;vertical-align:middle}.canvas-tools .canvas-tools__panel>.colors>.color-selected{width:28px;height:28px;border:1px solid #d2d5d7;padding:2px;background-color:#fff}.canvas-tools .canvas-tools__panel>.colors>.color-selected>i{display:block;width:100%;height:100%;transition:all .2s}.canvas-tools .canvas-tools__panel>.colors>.color-list{margin-left:4px;height:28px}.canvas-tools .canvas-tools__panel>.colors>.color-list>ul{list-style-type:none;margin:0;padding:0}.canvas-tools .canvas-tools__panel>.colors>.color-list>ul>li{display:inline-block;vertical-align:middle;width:13px;height:13px;border:1px solid #d2d5d7;margin-right:2px}.canvas-tools .canvas-tools__panel>.colors>.color-list>ul>li:last-child{margin-right:0}.canvas-tools .canvas-tools__panel>.colors>.color-list>ul>li:hover{-webkit-transform:scale(1.2);transform:scale(1.2);border-color:#fff}.canvas-tools .canvas-tools__panel>.colors>.color-list>ul:last-child{margin-top:2px}.canvas-tools .canvas-tools__panel>.ambiguite-range{font-size:12px;display:inline-block;vertical-align:middle;margin-left:8px}.canvas-tools .canvas-tools__panel>.ambiguite-range input,.canvas-tools .canvas-tools__panel>.ambiguite-range span{display:inline-block;vertical-align:middle}.canvas-tools .canvas-tools__panel>.ambiguite-range input{width:100px;margin-left:5px}.canvas-cursor__brush{cursor:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABGdBTUEAALGPC/xhBQAAARpJREFUOBGdkr1KA0EUhTcxEkSQQIpAihQWPoedD2BnIVsZQiBVEkiR7SwtBDsJqXwBLQwEgoWNjY2FjYUQ38Ei5Oc7sBeGLTKze+Djzsyecxh2N4ryq0zkHpYQQ2H1SW5TNsxukaZzQsewACvTvIBg3eFUaABHMEv3OruBIN3icm8wZl+FZ3iBQ/AqweGW2FrlKlChV0McFsxOvaegm/T2lLzzTC/dqzaO7A1s/8GzE28Dhhj0f1jQnZ+c18CrKxxrcMO2/uK87m3AcAkrsKA7vzlvQJAmuNywrX84bwY1YBrBATyCFWj+QguCJKNCU1DZQ7r/Y55CsK5x2i2eWFcggTPIJd3Eiv5Zd3KlU3OJOQd9lVd4A5Xl1g4YG2GGhwRfegAAAABJRU5ErkJggg==) 0 17,default;cursor:-webkit-image-set(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABGdBTUEAALGPC/xhBQAAARpJREFUOBGdkr1KA0EUhTcxEkSQQIpAihQWPoedD2BnIVsZQiBVEkiR7SwtBDsJqXwBLQwEgoWNjY2FjYUQ38Ei5Oc7sBeGLTKze+Djzsyecxh2N4ryq0zkHpYQQ2H1SW5TNsxukaZzQsewACvTvIBg3eFUaABHMEv3OruBIN3icm8wZl+FZ3iBQ/AqweGW2FrlKlChV0McFsxOvaegm/T2lLzzTC/dqzaO7A1s/8GzE28Dhhj0f1jQnZ+c18CrKxxrcMO2/uK87m3AcAkrsKA7vzlvQJAmuNywrX84bwY1YBrBATyCFWj+QguCJKNCU1DZQ7r/Y55CsK5x2i2eWFcggTPIJd3Eiv5Zd3KlU3OJOQd9lVd4A5Xl1g4YG2GGhwRfegAAAABJRU5ErkJggg==) 1x,url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABGdBTUEAALGPC/xhBQAAARpJREFUOBGdkr1KA0EUhTcxEkSQQIpAihQWPoedD2BnIVsZQiBVEkiR7SwtBDsJqXwBLQwEgoWNjY2FjYUQ38Ei5Oc7sBeGLTKze+Djzsyecxh2N4ryq0zkHpYQQ2H1SW5TNsxukaZzQsewACvTvIBg3eFUaABHMEv3OruBIN3icm8wZl+FZ3iBQ/AqweGW2FrlKlChV0McFsxOvaegm/T2lLzzTC/dqzaO7A1s/8GzE28Dhhj0f1jQnZ+c18CrKxxrcMO2/uK87m3AcAkrsKA7vzlvQJAmuNywrX84bwY1YBrBATyCFWj+QguCJKNCU1DZQ7r/Y55CsK5x2i2eWFcggTPIJd3Eiv5Zd3KlU3OJOQd9lVd4A5Xl1g4YG2GGhwRfegAAAABJRU5ErkJggg==) 2x) 0 17,default}.canvas-cursor__mosaic{cursor:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGM0NFMkEyQTRCOTcxMUU3OTc2Mzg4ODUyQTFDQUVGNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGM0NFMkEyQjRCOTcxMUU3OTc2Mzg4ODUyQTFDQUVGNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkYzQ0UyQTI4NEI5NzExRTc5NzYzODg4NTJBMUNBRUY3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkYzQ0UyQTI5NEI5NzExRTc5NzYzODg4NTJBMUNBRUY3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+BtIaIAAAA8pJREFUeNrEl99LU2Ecxs9++IOcmdZkieg2CdmchWgWSCBpFIRk3kldCKLhdIpo/hMVQkujRNlFoHeVdqGQIlRQpijlDxRsmpJjymhtlem2eh45iyUSJ93WFx7PPBvn87zve855n69Mr9cLEisBKoGKoBwoAzosfvcF+gjNQK+gYeirlIvKJBjQQg1QuUKhSJTJZHIc5TKx+IOfYvn9/gAOARw9OP0Eugct7ddAHNQKxg25XB4fExOjSE1NXczJyZkuLCz8kJeXt67T6XZGabfbEyYnJ9VjY2P6mZkZk9PpzNre3vYHAoFNGHqIn9yCfvyLAY66CyM9GRsbq8zOzn5TV1c3VFpa6pQyrcPDw+rOzs5L8/PzZ7a2tnyYkXc4XbPXbOxlIBd6pFQq05KTk11NTU22ysrKJWEf1dvbq21vb692u91JPp/vE05dh97/zQBHPoDpPp6RkbHQ09PTlZ6evikcoGZnZ1X19fW1a2trOizLGk6Vhc5EqIF46BlGfiotLc0+MDBwV6VS+YUwlNfrVZSXlzesrKxkYSa4HJehnYHJQ37XijXPTUpKcnd0dHSFC87itWw224OUlBQXGCaygt8FDWhxt9fyhmtubu42Go1eIczFpWxsbLSRQZa43L8NNPBR492+3xtOSvHaYLwmC/9aggZUfMnwObdYLINChAuMIbLw8QrZNHCebziNRrNYXFy8HmkDZJBFJtk0cI6vV4PBMC1Eqcgik2z+MfDdnp+fb4+WAbzG7WSSLRefAFlBQYEzWgawlzjFjUxLA9zhZJmZmd+iZYCbmGggUS7856IBD/fy5eXlQ9GCcvsmk2waWOI/4+PjqdEyQJZoYIkG5phkJiYmdNEyQBaZZNPAC8aoubk5U7QMkEUm2TQwwgzncDiyRkdH1ZGGk0EWmNzwRmiAH54yw1mt1ouRNkAGWWJo9QYfQysDJDLcWcaoSMH7+voymRPJEhOzoEDu4/Ezcz/W5fTU1JSxqKjorVqt3gonfHV1Nb6lpaUB6SgOBu7jVP/uRHQb6zKNAHkEGa6GMSpccF6rqqqq1uVyHSUDp+7sTkSCmNGqkdkcCJD6iooKM45xB4VjylVlZWVNmIETvDYZ0Pfg98ElCBaX4iWm6ILH49H29/cXIM/ZTSaTe79r3tbWZtnY2NCIsfwatCC1MelmgAw2JmazebCkpERSYBEbk4u8qcXGZFocuaTGJLQ1u8kAuUdrtog9fWNXa3YMrVlWuFqzP3ZPqB66ihlRSWxOaeyx+KjZD9odh7bnpRLb8+dS2/NfAgwAg5rkol00QQkAAAAASUVORK5CYII=) 0 17,default;cursor:-webkit-image-set(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGM0NFMkEyQTRCOTcxMUU3OTc2Mzg4ODUyQTFDQUVGNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGM0NFMkEyQjRCOTcxMUU3OTc2Mzg4ODUyQTFDQUVGNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkYzQ0UyQTI4NEI5NzExRTc5NzYzODg4NTJBMUNBRUY3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkYzQ0UyQTI5NEI5NzExRTc5NzYzODg4NTJBMUNBRUY3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+BtIaIAAAA8pJREFUeNrEl99LU2Ecxs9++IOcmdZkieg2CdmchWgWSCBpFIRk3kldCKLhdIpo/hMVQkujRNlFoHeVdqGQIlRQpijlDxRsmpJjymhtlem2eh45iyUSJ93WFx7PPBvn87zve855n69Mr9cLEisBKoGKoBwoAzosfvcF+gjNQK+gYeirlIvKJBjQQg1QuUKhSJTJZHIc5TKx+IOfYvn9/gAOARw9OP0Eugct7ddAHNQKxg25XB4fExOjSE1NXczJyZkuLCz8kJeXt67T6XZGabfbEyYnJ9VjY2P6mZkZk9PpzNre3vYHAoFNGHqIn9yCfvyLAY66CyM9GRsbq8zOzn5TV1c3VFpa6pQyrcPDw+rOzs5L8/PzZ7a2tnyYkXc4XbPXbOxlIBd6pFQq05KTk11NTU22ysrKJWEf1dvbq21vb692u91JPp/vE05dh97/zQBHPoDpPp6RkbHQ09PTlZ6evikcoGZnZ1X19fW1a2trOizLGk6Vhc5EqIF46BlGfiotLc0+MDBwV6VS+YUwlNfrVZSXlzesrKxkYSa4HJehnYHJQ37XijXPTUpKcnd0dHSFC87itWw224OUlBQXGCaygt8FDWhxt9fyhmtubu42Go1eIczFpWxsbLSRQZa43L8NNPBR492+3xtOSvHaYLwmC/9aggZUfMnwObdYLINChAuMIbLw8QrZNHCebziNRrNYXFy8HmkDZJBFJtk0cI6vV4PBMC1Eqcgik2z+MfDdnp+fb4+WAbzG7WSSLRefAFlBQYEzWgawlzjFjUxLA9zhZJmZmd+iZYCbmGggUS7856IBD/fy5eXlQ9GCcvsmk2waWOI/4+PjqdEyQJZoYIkG5phkJiYmdNEyQBaZZNPAC8aoubk5U7QMkEUm2TQwwgzncDiyRkdH1ZGGk0EWmNzwRmiAH54yw1mt1ouRNkAGWWJo9QYfQysDJDLcWcaoSMH7+voymRPJEhOzoEDu4/Ezcz/W5fTU1JSxqKjorVqt3gonfHV1Nb6lpaUB6SgOBu7jVP/uRHQb6zKNAHkEGa6GMSpccF6rqqqq1uVyHSUDp+7sTkSCmNGqkdkcCJD6iooKM45xB4VjylVlZWVNmIETvDYZ0Pfg98ElCBaX4iWm6ILH49H29/cXIM/ZTSaTe79r3tbWZtnY2NCIsfwatCC1MelmgAw2JmazebCkpERSYBEbk4u8qcXGZFocuaTGJLQ1u8kAuUdrtog9fWNXa3YMrVlWuFqzP3ZPqB66ihlRSWxOaeyx+KjZD9odh7bnpRLb8+dS2/NfAgwAg5rkol00QQkAAAAASUVORK5CYII=) 1x,url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGM0NFMkEyQTRCOTcxMUU3OTc2Mzg4ODUyQTFDQUVGNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGM0NFMkEyQjRCOTcxMUU3OTc2Mzg4ODUyQTFDQUVGNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkYzQ0UyQTI4NEI5NzExRTc5NzYzODg4NTJBMUNBRUY3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkYzQ0UyQTI5NEI5NzExRTc5NzYzODg4NTJBMUNBRUY3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+BtIaIAAAA8pJREFUeNrEl99LU2Ecxs9++IOcmdZkieg2CdmchWgWSCBpFIRk3kldCKLhdIpo/hMVQkujRNlFoHeVdqGQIlRQpijlDxRsmpJjymhtlem2eh45iyUSJ93WFx7PPBvn87zve855n69Mr9cLEisBKoGKoBwoAzosfvcF+gjNQK+gYeirlIvKJBjQQg1QuUKhSJTJZHIc5TKx+IOfYvn9/gAOARw9OP0Eugct7ddAHNQKxg25XB4fExOjSE1NXczJyZkuLCz8kJeXt67T6XZGabfbEyYnJ9VjY2P6mZkZk9PpzNre3vYHAoFNGHqIn9yCfvyLAY66CyM9GRsbq8zOzn5TV1c3VFpa6pQyrcPDw+rOzs5L8/PzZ7a2tnyYkXc4XbPXbOxlIBd6pFQq05KTk11NTU22ysrKJWEf1dvbq21vb692u91JPp/vE05dh97/zQBHPoDpPp6RkbHQ09PTlZ6evikcoGZnZ1X19fW1a2trOizLGk6Vhc5EqIF46BlGfiotLc0+MDBwV6VS+YUwlNfrVZSXlzesrKxkYSa4HJehnYHJQ37XijXPTUpKcnd0dHSFC87itWw224OUlBQXGCaygt8FDWhxt9fyhmtubu42Go1eIczFpWxsbLSRQZa43L8NNPBR492+3xtOSvHaYLwmC/9aggZUfMnwObdYLINChAuMIbLw8QrZNHCebziNRrNYXFy8HmkDZJBFJtk0cI6vV4PBMC1Eqcgik2z+MfDdnp+fb4+WAbzG7WSSLRefAFlBQYEzWgawlzjFjUxLA9zhZJmZmd+iZYCbmGggUS7856IBD/fy5eXlQ9GCcvsmk2waWOI/4+PjqdEyQJZoYIkG5phkJiYmdNEyQBaZZNPAC8aoubk5U7QMkEUm2TQwwgzncDiyRkdH1ZGGk0EWmNzwRmiAH54yw1mt1ouRNkAGWWJo9QYfQysDJDLcWcaoSMH7+voymRPJEhOzoEDu4/Ezcz/W5fTU1JSxqKjorVqt3gonfHV1Nb6lpaUB6SgOBu7jVP/uRHQb6zKNAHkEGa6GMSpccF6rqqqq1uVyHSUDp+7sTkSCmNGqkdkcCJD6iooKM45xB4VjylVlZWVNmIETvDYZ0Pfg98ElCBaX4iWm6ILH49H29/cXIM/ZTSaTe79r3tbWZtnY2NCIsfwatCC1MelmgAw2JmazebCkpERSYBEbk4u8qcXGZFocuaTGJLQ1u8kAuUdrtog9fWNXa3YMrVlWuFqzP3ZPqB66ihlRSWxOaeyx+KjZD9odh7bnpRLb8+dS2/NfAgwAg5rkol00QQkAAAAASUVORK5CYII=) 2x) 0 17,default}.canvas-cursor__crosshair{cursor:crosshair}.canvas-cursor__font{cursor:text}.canvas-cursor__default{cursor:default}.canvas-tools_text_helper{display:none;position:absolute;border:1px dashed #000;padding:2px;outline:none;min-width:16px;min-height:16px;line-height:16px;resize:none;overflow:hidden;background-color:transparent} -------------------------------------------------------------------------------- /src/js/main.js: -------------------------------------------------------------------------------- 1 | import '../scss/canvastools.scss' 2 | import './closet' 3 | import utils from './utils' 4 | import Template from './template' 5 | 6 | //stroke类型操作 7 | const STROKE_TYPES = ['rect', 'ellipse', 'brush', 'arrow', 'mosaic', 'font', 'rubber'] 8 | 9 | //默认颜色 10 | const STROKE_DEFAULT_COLOR = '#fb3838' 11 | 12 | //默认画笔大小 13 | const STROKE_DEFAULT_WIDTH = 2 14 | 15 | //辅助输入框padding值 16 | const TEXT_HELPER_PADDING = 2 17 | 18 | //辅助输入框层级 19 | const TEXT_HELPER_ZINDEX = 1990 20 | 21 | //辅助输入框ID 22 | const TEXT_HELPER_ID = 'canvas-tools_text_helper' 23 | 24 | //输入框默认字体大小 25 | //以设置的字体大小为准,改值仅做辅助值 26 | const TEXT_HELPER_FONT_SIZE = 12 27 | 28 | //字体 29 | const TEXT_FONT_FAMILY = '"Helvetica Neue",Helvetica,Arial,"Hiragino Sans GB","Hiragino Sans GB W3","WenQuanYi Micro Hei",sans-serif' 30 | 31 | //马赛克模糊度 32 | const AMBIGUITY_LEVEL = .7 33 | 34 | /** 35 | * 创建画笔+颜色容器 36 | * @param {Number} stroke [默认画笔] 37 | * @param {String} color [默认颜色] 38 | * @return {HTMLElement} 39 | */ 40 | const buildStrokePanel = (stroke = STROKE_DEFAULT_COLOR, color = STROKE_DEFAULT_COLOR) => { 41 | const el = document.createElement('div') 42 | el.className = 'canvas-tools__panel js-panel__stroke' 43 | el.style.cssText += 'white-space: nowrap!important;' 44 | el.innerHTML = Template.getStrokePanel(stroke) + Template.getColorPanel(color) 45 | return el 46 | } 47 | 48 | /** 49 | * 创建字号+颜色容器 50 | * @param {Number} fontSize [默认字号] 51 | * @param {String} color [默认颜色] 52 | * @return {HTMLElement} 53 | */ 54 | const buildFontPanel = (fontSize = TEXT_HELPER_FONT_SIZE, color = STROKE_DEFAULT_COLOR) => { 55 | const el = document.createElement('div') 56 | el.className = 'canvas-tools__panel js-panel__font' 57 | el.style.cssText += 'white-space: nowrap!important;' 58 | el.innerHTML = Template.getFontPanel(fontSize) + Template.getColorPanel(color) 59 | return el 60 | } 61 | 62 | /** 63 | * 创建画笔 + 模糊度容器 64 | * @param {Number} stroke [默认画笔] 65 | * @param {Number} ambiguity [默认模糊度] 66 | * @return {HTMLElement} 67 | */ 68 | const buildAmbiguityPanel = (stroke = STROKE_DEFAULT_COLOR, ambiguity = AMBIGUITY_LEVEL) => { 69 | const el = document.createElement('div') 70 | el.className = 'canvas-tools__panel js-panel__mosaic' 71 | el.style.cssText += 'white-space: nowrap!important;' 72 | el.innerHTML = Template.getStrokePanel(stroke) + Template.getAmbiguity(ambiguity) 73 | return el 74 | } 75 | 76 | /** 77 | * 创建辅助文本输入框 78 | * @return {HTMLElement} 79 | */ 80 | const getTextHelper = () => { 81 | let $textHelper = document.getElementById(TEXT_HELPER_ID) 82 | if (!$textHelper) { 83 | $textHelper = document.createElement('div') 84 | $textHelper.setAttribute('contenteditable', 'plaintext-only') 85 | $textHelper.setAttribute('spellcheck', 'false') 86 | $textHelper.setAttribute('id', TEXT_HELPER_ID) 87 | $textHelper.className = TEXT_HELPER_ID 88 | document.body.appendChild($textHelper) 89 | } 90 | $textHelper.innerHTML = '' 91 | $textHelper.style.cssText = 'display: block'; 92 | return $textHelper 93 | } 94 | 95 | /** 96 | * 初始化辅助文本输入框样式 97 | * @param {MouseEvent} event [鼠标事件] 98 | * @param {Object} state [instance state] 99 | * @param {Object} rect [instance rect] 100 | * @return {HTMLElement} 101 | */ 102 | const insertTextHelper = (event, state, rect) => { 103 | const $textHelper = getTextHelper(), 104 | threshold = state.fontSize || TEXT_HELPER_FONT_SIZE, 105 | padding = 2 * TEXT_HELPER_PADDING + 2, 106 | pos = getPos(event, rect) 107 | 108 | let x = event.pageX, 109 | y = event.pageY, 110 | maxW = Math.floor(rect.width - pos.x) - padding, 111 | maxH = Math.floor(rect.height - pos.y) - padding 112 | 113 | if (maxW <= threshold) { 114 | x -= (threshold + padding - maxW) 115 | maxW = threshold 116 | } 117 | 118 | if (maxH <= threshold) { 119 | y -= (threshold + padding - maxH) 120 | maxH = threshold 121 | } 122 | 123 | if (maxW >= rect.width) { 124 | maxW = rect.width - pos.x 125 | } 126 | 127 | if (maxH >= rect.height) { 128 | maxH = rect.height - pos.y 129 | } 130 | 131 | const styleMap = { 132 | 'font-size': threshold + 'px', 133 | 'line-height': threshold + 'px', 134 | 'min-width': threshold + 'px', 135 | 'min-height': threshold + 'px', 136 | 'max-width': maxW + 'px', 137 | 'max-height': maxH + 'px', 138 | 'z-index': TEXT_HELPER_ZINDEX, 139 | 'font-family': TEXT_FONT_FAMILY, 140 | display: 'block', 141 | position: 'absolute', 142 | top: y + 'px', 143 | left: x + 'px', 144 | color: state.strokeColor, 145 | padding: TEXT_HELPER_PADDING + 'px', 146 | overflow: 'hidden', 147 | } 148 | 149 | let style = '' 150 | 151 | for (let key in styleMap) { 152 | style += `${key}:${styleMap[key]};` 153 | } 154 | 155 | $textHelper.style.cssText = style 156 | $textHelper.focus() 157 | return $textHelper 158 | } 159 | 160 | /** 161 | * 移除辅助文本输入框 162 | * @return 163 | */ 164 | const removeTextHelper = () => { 165 | let $textHelper = document.getElementById(TEXT_HELPER_ID) 166 | if (!$textHelper) { 167 | return 168 | } 169 | $textHelper.innerHTML = '' 170 | $textHelper.style.cssText = 'display: none'; 171 | } 172 | 173 | 174 | /** 175 | * 获取鼠标在Canvas上的位置 176 | * @param {Element Event} event [鼠标事件] 177 | * @param {Object} rect [Canvas rect] 178 | * @return {Object} 179 | */ 180 | const getPos = (event, rect) => { 181 | let x = event.pageX - rect.left 182 | let y = event.pageY - rect.top 183 | if (x <= 0) x = 0 184 | if (x >= rect.width) x = rect.width 185 | if (y <= 0) y = 0 186 | if (y >= rect.height) y = rect.height 187 | 188 | return { 189 | x, 190 | y 191 | } 192 | } 193 | 194 | 195 | /** 196 | * 默认配置 197 | * @type {Object} 198 | */ 199 | const defaults = { 200 | //工具条父级对象容器 201 | container: document.body, 202 | //显示按钮 203 | buttons: ['rect', 'ellipse', 'brush', 'font', 'mosaic', 'undo', 'save'] 204 | } 205 | 206 | //创建一个下载链接 207 | const $saveLink = document.createElementNS('http://www.w3.org/1999/xhtml', 'a') 208 | 209 | //是否支持原生下载 210 | const canUseSaveLink = 'download' in $saveLink 211 | 212 | //下载文件 213 | const __downloadFile = function() { 214 | const fileName = `canvas_${Date.now()}.png` 215 | const canvas = this.canvas 216 | 217 | if (canUseSaveLink) { 218 | let fileUrl = canvas.toDataURL('png') 219 | fileUrl = fileUrl.replace('image/png', 'image/octet-stream') 220 | setTimeout(() => { 221 | $saveLink.href = fileUrl 222 | $saveLink.download = fileName 223 | 224 | //触发click事件 225 | $saveLink.dispatchEvent(new MouseEvent('click')) 226 | }) 227 | } 228 | 229 | //for ie 10+ 230 | else if (typeof navigator !== "undefined" && typeof canvas.msToBlob === 'function' && navigator.msSaveBlob) { 231 | navigator.msSaveBlob(canvas.msToBlob(), fileName) 232 | } 233 | 234 | // other 235 | else { 236 | console.log('您的浏览器不支持该操作') 237 | } 238 | } 239 | 240 | 241 | //相关事件绑定 242 | function __bindEvents() { 243 | const self = this 244 | const { 245 | canvas, 246 | context, 247 | $el, 248 | state, 249 | config, 250 | rect, 251 | _handles, 252 | history 253 | } = this 254 | 255 | const $btns = utils.$('.js-btn', $el), 256 | $fontPanel = utils.$('.js-panel__font', $el)[0], 257 | $strokePanel = utils.$('.js-panel__stroke', $el)[0], 258 | $mosaicPanel = utils.$('.js-panel__mosaic', $el)[0], 259 | $colorSelected = utils.$('.js-color-selected', $el), 260 | $colors = utils.$('.js-color', $el), 261 | $strokeWidth = utils.$('.js-stroke-width', $el), 262 | $fontSize = utils.$('.js-font-size', $el), 263 | $mosaicAmbiguity = utils.$('.js-mosaic-ambiguity', $el) 264 | 265 | //按钮事件 266 | _handles.btnEmit = function(event) { 267 | event.stopPropagation() 268 | if (state.drawType === 'font') { 269 | __drawFont.call(self, event) 270 | } 271 | const panel = this.getAttribute('data-panel') 272 | const value = this.getAttribute('data-value') 273 | utils.each($btns, (index, $btn) => { 274 | if ($btn !== this) { 275 | utils.classList($btn, 'remove', 'active') 276 | } 277 | }) 278 | if (!!~STROKE_TYPES.indexOf(value)) { 279 | state.drawType = value 280 | } 281 | if (panel) { 282 | utils.classList(this, 'toggle', 'active') 283 | const isActive = /active/.test(this.className) 284 | const visible = isActive ? 'block' : 'none' 285 | if (panel === 'stroke') { 286 | $fontPanel.style.display = 'none' 287 | $mosaicPanel.style.display = 'none' 288 | $strokePanel.style.display = visible 289 | } else if (panel === 'font') { 290 | $fontPanel.style.display = visible 291 | $strokePanel.style.display = 'none' 292 | $mosaicPanel.style.display = 'none' 293 | } else if (panel === 'mosaic') { 294 | $mosaicPanel.style.display = visible 295 | $fontPanel.style.display = 'none' 296 | $strokePanel.style.display = 'none' 297 | } 298 | } else { 299 | //$fontPanel.style.display = 'none' 300 | //$strokePanel.style.display = 'none' 301 | } 302 | 303 | if (value === 'save') { 304 | __downloadFile.call(self) 305 | return 306 | } 307 | 308 | //history[0]是画布的初始状态 309 | //因此只有多于1个历史记录时才可以恢复上一步 310 | if (value === 'undo' && history.length > 1) { 311 | history.pop() 312 | context.putImageData(history[history.length - 1], 0, 0, 0, 0, rect.width, rect.height) 313 | } 314 | 315 | __toggleCanvasCursor.call(self) 316 | } 317 | 318 | _handles.toggleColor = function(event) { 319 | const color = this.getAttribute('data-value') 320 | state.strokeColor = color 321 | utils.each($colorSelected, (index, item) => item.style.background = color) 322 | } 323 | 324 | _handles.toggleStrokeWidth = function(event) { 325 | state.strokeWidth = Number(this.getAttribute('data-value')) 326 | utils.each($strokeWidth, (index, item) => { 327 | const value = Number(item.getAttribute('data-value')) 328 | const method = value === state.strokeWidth ? 'add' : 'remove' 329 | utils.classList(item, method, 'active') 330 | }) 331 | } 332 | 333 | _handles.toggleFontSize = function(event) { 334 | state.fontSize = Number(this.value) 335 | } 336 | 337 | //鼠标在画布上的初始位置 338 | let _startPos 339 | 340 | _handles.onMouseDown = function(event) { 341 | if (!!~STROKE_TYPES.indexOf(state.drawType) === false || state.drawType === 'font') { 342 | return 343 | } 344 | _startPos = getPos(event, rect) 345 | 346 | //保存当前快照 347 | state.lastImageData = context.getImageData(0, 0, rect.width, rect.height) 348 | 349 | //初始化context状态 350 | context.lineCap = 'round' 351 | context.lineJoin = 'round' 352 | context.shadowBlur = 0 353 | context.strokeStyle = state.strokeColor 354 | context.lineWidth = state.strokeWidth 355 | switch (state.drawType) { 356 | case 'rect': 357 | __drawRect.call(self, event, _startPos) 358 | break 359 | case 'ellipse': 360 | __drawEllipse.call(self, event, _startPos) 361 | break 362 | case 'mosaic': 363 | __drawMoasic.call(self, event) 364 | break 365 | case 'brush': 366 | default: 367 | __drawBrush.call(self, event, _startPos) 368 | break 369 | } 370 | 371 | utils.$on(document, 'mousemove', _handles.onMouseMove) 372 | utils.$on(document, 'mouseup', _handles.onMouseUp) 373 | } 374 | 375 | _handles.onMouseMove = function(event) { 376 | if (!!~STROKE_TYPES.indexOf(state.drawType) === false || state.drawType === 'font') { 377 | return 378 | } 379 | switch (state.drawType) { 380 | case 'rect': 381 | __drawRect.call(self, event, _startPos) 382 | break 383 | case 'ellipse': 384 | __drawEllipse.call(self, event, _startPos) 385 | break 386 | case 'mosaic': 387 | __drawMoasic.call(self, event) 388 | break 389 | case 'brush': 390 | default: 391 | __drawBrush.call(self, event, null) 392 | break 393 | } 394 | } 395 | 396 | _handles.onMouseUp = function(event) { 397 | utils.$off(document, 'mousemove', _handles.onMouseMove) 398 | utils.$off(document, 'mouseup', _handles.onMouseUp) 399 | if (!!~STROKE_TYPES.indexOf(state.drawType) && state.drawType !== 'font') { 400 | __pushHistory.call(self) 401 | } 402 | } 403 | 404 | _handles.insertTextHelper = function(event) { 405 | event.stopPropagation() 406 | if (state.drawType !== 'font') { 407 | return 408 | } 409 | if (state.isEntry) { 410 | __drawFont.call(self, event) 411 | return 412 | } 413 | insertTextHelper(event, state, rect) 414 | state.isEntry = true 415 | } 416 | 417 | _handles.removeTextHelper = function(event) { 418 | if (state.drawType !== 'font') { 419 | removeTextHelper() 420 | } else if (!event.target.closest('#canvas-tools-input')) { 421 | __drawFont.call(self, event) 422 | } 423 | } 424 | 425 | _handles.resize = function(event) { 426 | const _rect = canvas.getBoundingClientRect() 427 | rect.width = canvas.width 428 | rect.height = canvas.height 429 | rect.offsetWidth = canvas.offsetWidth 430 | rect.offsetHeight = canvas.offsetHeight 431 | rect.top = _rect.top 432 | rect.left = _rect.left 433 | state.drawType === 'font' && state.isEntry && __drawFont.call(self, event) 434 | } 435 | 436 | _handles.toggleAmbiguity = function(event) { 437 | state.ambiguity = this.value 438 | console.log(state) 439 | } 440 | 441 | //按钮事件 442 | utils.$on($btns, 'click', _handles.btnEmit) 443 | 444 | //切换颜色 445 | utils.$on($colors, 'click', _handles.toggleColor) 446 | 447 | //切换画笔大小 448 | utils.$on($strokeWidth, 'click', _handles.toggleStrokeWidth) 449 | 450 | //切换字体大小 451 | utils.$on($fontSize, 'change', _handles.toggleFontSize) 452 | 453 | utils.$on($mosaicAmbiguity, 'change', _handles.toggleAmbiguity) 454 | 455 | //矩形,椭圆,画笔等绘制 456 | utils.$on(canvas, 'mousedown', _handles.onMouseDown) 457 | 458 | //插入文本辅助框 459 | utils.$on(canvas, 'click', _handles.insertTextHelper) 460 | 461 | //移除文本辅助框 462 | utils.$on(document, 'click', _handles.removeTextHelper) 463 | 464 | //window resize 465 | window.addEventListener('resize', _handles.resize) 466 | } 467 | 468 | /** 469 | * 绘制矩形 470 | * @param {MouseEvent} event [鼠标事件] 471 | * @param {Object} start [起始位置] 472 | * @return 473 | */ 474 | function __drawRect(event, start) { 475 | const { 476 | context, 477 | rect, 478 | state 479 | } = this 480 | const pos = getPos(event, rect) 481 | let width = pos.x - start.x 482 | let height = pos.y - start.y 483 | context.clearRect(0, 0, rect.width, rect.height) 484 | context.putImageData(state.lastImageData, 0, 0, 0, 0, rect.width, rect.height) 485 | context.save() 486 | context.beginPath() 487 | context.strokeRect(start.x, start.y, width, height) 488 | context.restore() 489 | context.closePath() 490 | } 491 | 492 | 493 | /** 494 | * 绘制椭圆 495 | * @param {MouseEvent} event [鼠标事件] 496 | * @param {Object} start [起始位置] 497 | * @return 498 | */ 499 | function __drawEllipse(event, start) { 500 | const { 501 | context, 502 | rect, 503 | state 504 | } = this 505 | const pos = getPos(event, rect) 506 | let scaleX = 1 * ((pos.x - start.x) / 2) 507 | let scaleY = 1 * ((pos.y - start.y) / 2) 508 | let x = (start.x / scaleX) + 1 509 | let y = (start.y / scaleY) + 1 510 | context.clearRect(0, 0, rect.width, rect.height) 511 | context.putImageData(state.lastImageData, 0, 0, 0, 0, rect.width, rect.height) 512 | context.save() 513 | context.beginPath() 514 | context.scale(scaleX, scaleY) 515 | context.arc(x, y, 1, 0, 2 * Math.PI) 516 | context.restore() 517 | context.closePath() 518 | context.stroke() 519 | } 520 | 521 | 522 | /** 523 | * 画笔工具自由绘制 524 | * @param {MouseEvent} event [鼠标事件] 525 | * @param {Object | null} start [起始位置] 526 | * @return 527 | */ 528 | function __drawBrush(event, start = null) { 529 | const { 530 | context, 531 | rect 532 | } = this 533 | if (start) { 534 | context.beginPath() 535 | context.moveTo(start.x, start.y) 536 | } else { 537 | const pos = getPos(event, rect) 538 | context.lineTo(pos.x, pos.y) 539 | context.stroke() 540 | } 541 | } 542 | 543 | 544 | /** 545 | * 绘制文字 546 | * @param {MouseEvent} event [鼠标事件] 547 | * @return {[type]} 548 | */ 549 | function __drawFont(event) { 550 | const $textHelper = document.getElementById(TEXT_HELPER_ID) 551 | if (!$textHelper) { 552 | this.state.isEntry = false 553 | return 554 | } 555 | const content = $textHelper.textContent.trim() 556 | const length = content.length 557 | if (!content || !length) { 558 | this.state.isEntry = false 559 | removeTextHelper() 560 | return 561 | } 562 | const style = utils.getComputedStyles($textHelper), 563 | threshold = this.state.fontSize || TEXT_HELPER_FONT_SIZE, 564 | padding = 2 * TEXT_HELPER_PADDING, 565 | context = this.context 566 | 567 | let x = parseFloat(style.left) - this.rect.left + padding - 2, 568 | y = parseFloat(style.top) - this.rect.top + threshold, 569 | lineWidth = 0, 570 | lastSubStrIndex = 0 571 | 572 | context.beginPath() 573 | context.save() 574 | context.fillStyle = this.state.strokeColor 575 | context.font = `${this.state.fontSize}px ${TEXT_FONT_FAMILY}` 576 | 577 | for (let i = 0; i < length; i++) { 578 | let char = content[i] 579 | 580 | //让文字自动换行 581 | lineWidth += context.measureText(char).width 582 | if (lineWidth > this.rect.width - x) { 583 | context.fillText(content.substring(lastSubStrIndex, i), x, y) 584 | y += threshold 585 | lineWidth = 0 586 | lastSubStrIndex = i 587 | } 588 | if (i == length - 1) { 589 | context.fillText(content.substring(lastSubStrIndex, i + 1), x, y); 590 | } 591 | } 592 | context.restore() 593 | context.closePath() 594 | this.state.isEntry = false 595 | removeTextHelper() 596 | __pushHistory.call(this) 597 | } 598 | 599 | 600 | /** 601 | * 绘制马赛克 602 | * @param {MouseEvent} event [鼠标事件] 603 | * @return {[type]} 604 | */ 605 | function __drawMoasic(event) { 606 | const { 607 | context, 608 | state, 609 | rect 610 | } = this 611 | const pos = getPos(event, rect) 612 | const size = state.strokeWidth * 3 613 | 614 | //获取当前位置1PX的颜色值 615 | const data = context.getImageData(pos.x, pos.y, size, size).data 616 | 617 | let r = 0, g = 0, b = 0 618 | 619 | for (let row = 0; row < size; row ++) { 620 | for (let col = 0; col < size; col++) { 621 | r += data[((size * row) + col) * 4] 622 | g += data[((size * row) + col) * 4 + 1] 623 | b += data[((size * row) + col) * 4 + 2] 624 | } 625 | } 626 | 627 | r = Math.round(r / (size * size)) 628 | g = Math.round(g / (size * size)) 629 | b = Math.round(b / (size * size)) 630 | const color = `rgba(${r}, ${g}, ${b}, ${state.ambiguity})` 631 | context.beginPath() 632 | context.save() 633 | context.fillStyle = color 634 | context.fillRect(pos.x, pos.y, size, size) 635 | context.restore() 636 | } 637 | 638 | 639 | /** 640 | * 切换鼠标指针 641 | * @return 642 | */ 643 | function __toggleCanvasCursor() { 644 | const canvas = this.canvas 645 | let cursor 646 | switch (this.state.drawType) { 647 | case 'brush': 648 | cursor = 'brush' 649 | break 650 | case 'font': 651 | cursor = 'font' 652 | break 653 | case 'mosaic': 654 | cursor = 'mosaic' 655 | break 656 | case 'rect': 657 | case 'ellipse': 658 | cursor = 'crosshair' 659 | break 660 | default: 661 | cursor = 'default' 662 | } 663 | if (cursor) { 664 | canvas.className = canvas.className.replace(/canvas-cursor__(\w+)/, '').trim() + ` canvas-cursor__${cursor}` 665 | } 666 | } 667 | 668 | 669 | /** 670 | * 保存到历史记录 671 | * @return 672 | */ 673 | function __pushHistory() { 674 | this.history.push(this.context.getImageData(0, 0, this.rect.width, this.rect.height)) 675 | } 676 | 677 | /** 678 | * CanvasTools 679 | * Class 680 | */ 681 | class CanvasTools { 682 | 683 | /** 684 | * constructor 685 | * @param {CanvasElement} canvas [canvas element object] 686 | * @param {Object} options [config] 687 | * @return {Object} [instance] 688 | */ 689 | constructor(canvas, options) { 690 | if (!canvas || typeof canvas.getContext !== 'function') { 691 | throw new Error('invalid canvas object') 692 | } 693 | this.canvas = canvas 694 | this.context = canvas.getContext('2d') 695 | this.config = utils.extend({}, defaults, options || {}) 696 | 697 | this.history = [] 698 | this.state = Object.create(null) 699 | this.rect = Object.create(null) 700 | this._handles = Object.create(null) 701 | 702 | this.state.strokeWidth = STROKE_DEFAULT_WIDTH 703 | this.state.fontSize = TEXT_HELPER_FONT_SIZE 704 | this.state.strokeColor = STROKE_DEFAULT_COLOR 705 | this.state.ambiguity = AMBIGUITY_LEVEL 706 | this.state.drawType = 'brush' 707 | this.state.isEntry = false 708 | 709 | this.rect.width = canvas.width 710 | this.rect.height = canvas.height 711 | 712 | this.rect.offsetWidth = canvas.offsetWidth 713 | this.rect.offsetHeight = canvas.offsetHeight 714 | 715 | const rect = canvas.getBoundingClientRect() 716 | this.rect.top = rect.top 717 | this.rect.left = rect.left 718 | 719 | //保存现场 720 | this.state.lastImageData = this.context.getImageData(0, 0, this.rect.width, this.rect.height) 721 | 722 | //将画布的初始状态保存到历史记录 723 | __pushHistory.call(this) 724 | 725 | __toggleCanvasCursor.call(this) 726 | 727 | this.render() 728 | } 729 | 730 | /** 731 | * 初始化工具条到DOM 732 | * @return 733 | */ 734 | render() { 735 | const C = this.config 736 | const S = this.state 737 | let el = document.createElement('div') 738 | el.className = 'canvas-tools' 739 | el.innerHTML = Template.getButtons(C.buttons) 740 | C.container.appendChild(el) 741 | 742 | this.$el = el 743 | this.$el.appendChild(buildStrokePanel(S.strokeWidth, S.strokeColor)) 744 | this.$el.appendChild(buildFontPanel(S.fontSize, S.strokeColor)) 745 | this.$el.appendChild(buildAmbiguityPanel(S.strokeWidth, S.ambiguity)) 746 | __bindEvents.call(this) 747 | } 748 | 749 | refresh() { 750 | 751 | } 752 | 753 | /** 754 | * destory 755 | * @return 756 | */ 757 | destory() { 758 | const { 759 | canvas, 760 | $el, 761 | _handles 762 | } = this 763 | 764 | const $btns = utils.$('.js-btn', $el), 765 | $colors = utils.$('.js-color', $el), 766 | $strokeWidth = utils.$('.js-stroke-width', $el), 767 | $fontSize = utils.$('.js-font-size', $el), 768 | $mosaicAmbiguity = utils.$('.js-mosaic-ambiguity', $el), 769 | $textHelper = document.getElementById(TEXT_HELPER_ID) 770 | 771 | utils.$off($btns, 'click', _handles.btnEmit) 772 | utils.$off($colors, 'click', _handles.toggleColor) 773 | utils.$off($strokeWidth, 'click', _handles.toggleStrokeWidth) 774 | utils.$off($fontSize, 'change', _handles.toggleFontSize) 775 | utils.$off($mosaicAmbiguity, 'change', _handles.toggleAmbiguity) 776 | utils.$off(canvas, 'mousedown', _handles.onMouseDown) 777 | utils.$off(canvas, 'click', _handles.insertTextHelper) 778 | utils.$off(document, 'click', _handles.removeTextHelper) 779 | window.removeEventListener('resize', _handles.resize) 780 | $textHelper && $textHelper.parentNoed.removeChild($textHelper) 781 | this.canvas = null 782 | this.context = null 783 | this.history.length = 0 784 | this.config.container.removeChild(this.$el) 785 | } 786 | 787 | } 788 | 789 | export default CanvasTools -------------------------------------------------------------------------------- /dist/canvastools.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * CanvasTools v1.0.0 3 | * (github)https://github.com/S-mohan/canvasTools 4 | * (url) https://smohan.net/lab/canvastools 5 | * (c) smohan 6 | * license MIT 7 | */ 8 | .canvas-tools, .canvas-tools * { 9 | -webkit-box-sizing: border-box; 10 | box-sizing: border-box; } 11 | 12 | @font-face { 13 | font-family: "CanvasTools"; 14 | src: url(data:application/font-woff;base64,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) format("woff"); } 15 | 16 | [class^="canvas-tools-icon__"], [class*=" canvas-tools-icon__"] { 17 | font-family: "CanvasTools" !important; 18 | font-size: 16px; 19 | font-style: normal; 20 | line-height: 1; 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; } 23 | 24 | .canvas-tools-icon__font:before { 25 | content: "\E620"; } 26 | 27 | .canvas-tools-icon__save:before { 28 | content: "\E60B"; } 29 | 30 | .canvas-tools-icon__undo:before { 31 | content: "\E631"; } 32 | 33 | .canvas-tools-icon__arrow:before { 34 | content: "\E50A"; } 35 | 36 | .canvas-tools-icon__rubber:before { 37 | content: "\E6B8"; } 38 | 39 | .canvas-tools-icon__rect:before { 40 | content: "\E619"; } 41 | 42 | .canvas-tools-icon__ellipse:before { 43 | content: "\E61A"; } 44 | 45 | .canvas-tools-icon__mosaic:before { 46 | content: "\E622"; } 47 | 48 | .canvas-tools-icon__brush:before { 49 | content: "\E69A"; } 50 | 51 | .canvas-tools { 52 | display: inline-block; 53 | border: 1px solid #dcdcdc; 54 | height: 2rem; 55 | position: relative; 56 | font-size: 0; 57 | padding: 3px 5px; 58 | -webkit-user-select: none; 59 | -moz-user-select: none; 60 | -ms-user-select: none; 61 | user-select: none; 62 | background: #fff; 63 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.075), 0 1px 4px rgba(0, 0, 0, 0.075); 64 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.075), 0 1px 4px rgba(0, 0, 0, 0.075); } 65 | .canvas-tools [class^="canvas-tools-icon__"], .canvas-tools [class*=" canvas-tools-icon__"] { 66 | font-size: 1.125rem; 67 | position: relative; } 68 | .canvas-tools .canvas-tools-btn { 69 | display: inline-block; 70 | vertical-align: middle; 71 | height: 1.5rem; 72 | padding: .15rem .2rem; 73 | border: 1px solid transparent; 74 | -webkit-transition: all .2s; 75 | transition: all .2s; 76 | margin-right: .5rem; 77 | position: relative; 78 | text-align: center; } 79 | .canvas-tools .canvas-tools-btn .btn-toggle { 80 | display: -webkit-box; 81 | display: -ms-flexbox; 82 | display: flex; 83 | height: 100%; 84 | text-decoration: none; 85 | outline: none; 86 | -webkit-box-orient: vertical; 87 | -webkit-box-direction: normal; 88 | -ms-flex-flow: cloumn; 89 | flex-flow: cloumn; 90 | -webkit-box-align: center; 91 | -ms-flex-align: center; 92 | align-items: center; 93 | -webkit-box-pack: center; 94 | -ms-flex-pack: center; 95 | justify-content: center; 96 | cursor: pointer; } 97 | .canvas-tools .canvas-tools-btn:hover, .canvas-tools .canvas-tools-btn.active { 98 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dcdcdc), to(#fff)); 99 | background-image: linear-gradient(to bottom, #dcdcdc, #fff); 100 | background-color: #efefef; 101 | border-color: #dedede; } 102 | .canvas-tools .canvas-tools-btn.active { 103 | border-color: #d2d5d7; } 104 | .canvas-tools .canvas-tools-btn .canvas-tools-icon__font { 105 | position: relative; 106 | top: -1px; } 107 | .canvas-tools .canvas-tools-btn .canvas-tools-icon__font, .canvas-tools .canvas-tools-btn .canvas-tools-icon__arrow { 108 | color: #2f96e0; } 109 | .canvas-tools .canvas-tools-btn .canvas-tools-icon__save { 110 | color: #7492d7; 111 | top: 1px; } 112 | .canvas-tools .canvas-tools-btn .canvas-tools-icon__undo { 113 | color: #68af45; } 114 | .canvas-tools .canvas-tools-btn .canvas-tools-icon__rect, .canvas-tools .canvas-tools-btn .canvas-tools-icon__ellipse { 115 | color: #8eb1c8; } 116 | .canvas-tools .canvas-tools-btn .canvas-tools-icon__rubber { 117 | color: #429fe2; } 118 | .canvas-tools .canvas-tools-btn .canvas-tools-icon__mosaic { 119 | color: #a6c2d8; } 120 | .canvas-tools .canvas-tools-btn .canvas-tools-icon__brush { 121 | color: #68af45; } 122 | 123 | .canvas-tools .canvas-tools__panel { 124 | font-size: 0; 125 | position: absolute; 126 | top: 100%; 127 | margin-top: 5px; 128 | left: 0; 129 | background-color: rgba(255, 255, 255, 0.95); 130 | border: 1px solid #d7dee2; 131 | cursor: default; 132 | width: auto; 133 | padding: 4px 10px; 134 | text-align: left; 135 | display: none; 136 | white-space: nowrap !important; 137 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 4px rgba(0, 0, 0, 0.05); 138 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 4px rgba(0, 0, 0, 0.05); } 139 | .canvas-tools .canvas-tools__panel > .font-select { 140 | display: inline-block; 141 | vertical-align: middle; 142 | padding: 3px 5px; 143 | border: 1px solid #d2d5d7; 144 | outline: none; } 145 | .canvas-tools .canvas-tools__panel > .strokes { 146 | display: inline-block; 147 | vertical-align: middle; 148 | padding-right: 4px; 149 | border-right: 1px solid #d2d5d7; } 150 | .canvas-tools .canvas-tools__panel > .strokes > .stroke { 151 | width: 24px; 152 | height: 24px; 153 | display: inline-block; 154 | vertical-align: middle; 155 | border: 1px solid transparent; 156 | text-align: center; 157 | cursor: pointer; 158 | -webkit-transition: all .2s; 159 | transition: all .2s; 160 | margin-right: 2px; } 161 | .canvas-tools .canvas-tools__panel > .strokes > .stroke:last-child { 162 | margin-right: 0; } 163 | .canvas-tools .canvas-tools__panel > .strokes > .stroke:hover, .canvas-tools .canvas-tools__panel > .strokes > .stroke.active { 164 | background-color: #dee1e8; 165 | border-color: #d7dee2; } 166 | .canvas-tools .canvas-tools__panel > .strokes > .stroke.active { 167 | border-color: #d2d5d7; } 168 | .canvas-tools .canvas-tools__panel > .strokes > .stroke:before, .canvas-tools .canvas-tools__panel > .strokes > .stroke > i { 169 | display: inline-block; 170 | vertical-align: middle; } 171 | .canvas-tools .canvas-tools__panel > .strokes > .stroke:before { 172 | content: ''; 173 | height: 100%; } 174 | .canvas-tools .canvas-tools__panel > .strokes > .stroke i { 175 | border-radius: 50%; 176 | background-color: #2f8ddd; } 177 | .canvas-tools .canvas-tools__panel > .colors { 178 | display: inline-block; 179 | vertical-align: middle; 180 | margin-left: 8px; } 181 | .canvas-tools .canvas-tools__panel > .colors > .color-selected, .canvas-tools .canvas-tools__panel > .colors > .color-list { 182 | display: inline-block; 183 | vertical-align: middle; } 184 | .canvas-tools .canvas-tools__panel > .colors > .color-selected { 185 | width: 28px; 186 | height: 28px; 187 | border: 1px solid #d2d5d7; 188 | padding: 2px; 189 | background-color: #fff; } 190 | .canvas-tools .canvas-tools__panel > .colors > .color-selected > i { 191 | display: block; 192 | width: 100%; 193 | height: 100%; 194 | -webkit-transition: all .2s; 195 | transition: all .2s; } 196 | .canvas-tools .canvas-tools__panel > .colors > .color-list { 197 | margin-left: 4px; 198 | height: 28px; } 199 | .canvas-tools .canvas-tools__panel > .colors > .color-list > ul { 200 | list-style-type: none; 201 | margin: 0; 202 | padding: 0; } 203 | .canvas-tools .canvas-tools__panel > .colors > .color-list > ul > li { 204 | display: inline-block; 205 | vertical-align: middle; 206 | width: 13px; 207 | height: 13px; 208 | border: 1px solid #d2d5d7; 209 | margin-right: 2px; } 210 | .canvas-tools .canvas-tools__panel > .colors > .color-list > ul > li:last-child { 211 | margin-right: 0; } 212 | .canvas-tools .canvas-tools__panel > .colors > .color-list > ul > li:hover { 213 | -webkit-transform: scale(1.2); 214 | transform: scale(1.2); 215 | border-color: #fff; } 216 | .canvas-tools .canvas-tools__panel > .colors > .color-list > ul:last-child { 217 | margin-top: 2px; } 218 | .canvas-tools .canvas-tools__panel > .ambiguite-range { 219 | font-size: 12px; 220 | display: inline-block; 221 | vertical-align: middle; 222 | margin-left: 8px; } 223 | .canvas-tools .canvas-tools__panel > .ambiguite-range span, .canvas-tools .canvas-tools__panel > .ambiguite-range input { 224 | display: inline-block; 225 | vertical-align: middle; } 226 | .canvas-tools .canvas-tools__panel > .ambiguite-range input { 227 | width: 100px; 228 | margin-left: 5px; } 229 | 230 | .canvas-cursor__brush { 231 | cursor: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABGdBTUEAALGPC/xhBQAAARpJREFUOBGdkr1KA0EUhTcxEkSQQIpAihQWPoedD2BnIVsZQiBVEkiR7SwtBDsJqXwBLQwEgoWNjY2FjYUQ38Ei5Oc7sBeGLTKze+Djzsyecxh2N4ryq0zkHpYQQ2H1SW5TNsxukaZzQsewACvTvIBg3eFUaABHMEv3OruBIN3icm8wZl+FZ3iBQ/AqweGW2FrlKlChV0McFsxOvaegm/T2lLzzTC/dqzaO7A1s/8GzE28Dhhj0f1jQnZ+c18CrKxxrcMO2/uK87m3AcAkrsKA7vzlvQJAmuNywrX84bwY1YBrBATyCFWj+QguCJKNCU1DZQ7r/Y55CsK5x2i2eWFcggTPIJd3Eiv5Zd3KlU3OJOQd9lVd4A5Xl1g4YG2GGhwRfegAAAABJRU5ErkJggg==) 0 17, default; 232 | cursor: -webkit-image-set(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABGdBTUEAALGPC/xhBQAAARpJREFUOBGdkr1KA0EUhTcxEkSQQIpAihQWPoedD2BnIVsZQiBVEkiR7SwtBDsJqXwBLQwEgoWNjY2FjYUQ38Ei5Oc7sBeGLTKze+Djzsyecxh2N4ryq0zkHpYQQ2H1SW5TNsxukaZzQsewACvTvIBg3eFUaABHMEv3OruBIN3icm8wZl+FZ3iBQ/AqweGW2FrlKlChV0McFsxOvaegm/T2lLzzTC/dqzaO7A1s/8GzE28Dhhj0f1jQnZ+c18CrKxxrcMO2/uK87m3AcAkrsKA7vzlvQJAmuNywrX84bwY1YBrBATyCFWj+QguCJKNCU1DZQ7r/Y55CsK5x2i2eWFcggTPIJd3Eiv5Zd3KlU3OJOQd9lVd4A5Xl1g4YG2GGhwRfegAAAABJRU5ErkJggg==) 1x, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABGdBTUEAALGPC/xhBQAAARpJREFUOBGdkr1KA0EUhTcxEkSQQIpAihQWPoedD2BnIVsZQiBVEkiR7SwtBDsJqXwBLQwEgoWNjY2FjYUQ38Ei5Oc7sBeGLTKze+Djzsyecxh2N4ryq0zkHpYQQ2H1SW5TNsxukaZzQsewACvTvIBg3eFUaABHMEv3OruBIN3icm8wZl+FZ3iBQ/AqweGW2FrlKlChV0McFsxOvaegm/T2lLzzTC/dqzaO7A1s/8GzE28Dhhj0f1jQnZ+c18CrKxxrcMO2/uK87m3AcAkrsKA7vzlvQJAmuNywrX84bwY1YBrBATyCFWj+QguCJKNCU1DZQ7r/Y55CsK5x2i2eWFcggTPIJd3Eiv5Zd3KlU3OJOQd9lVd4A5Xl1g4YG2GGhwRfegAAAABJRU5ErkJggg==) 2x) 0 17, default; } 233 | 234 | .canvas-cursor__mosaic { 235 | cursor: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGM0NFMkEyQTRCOTcxMUU3OTc2Mzg4ODUyQTFDQUVGNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGM0NFMkEyQjRCOTcxMUU3OTc2Mzg4ODUyQTFDQUVGNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkYzQ0UyQTI4NEI5NzExRTc5NzYzODg4NTJBMUNBRUY3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkYzQ0UyQTI5NEI5NzExRTc5NzYzODg4NTJBMUNBRUY3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+BtIaIAAAA8pJREFUeNrEl99LU2Ecxs9++IOcmdZkieg2CdmchWgWSCBpFIRk3kldCKLhdIpo/hMVQkujRNlFoHeVdqGQIlRQpijlDxRsmpJjymhtlem2eh45iyUSJ93WFx7PPBvn87zve855n69Mr9cLEisBKoGKoBwoAzosfvcF+gjNQK+gYeirlIvKJBjQQg1QuUKhSJTJZHIc5TKx+IOfYvn9/gAOARw9OP0Eugct7ddAHNQKxg25XB4fExOjSE1NXczJyZkuLCz8kJeXt67T6XZGabfbEyYnJ9VjY2P6mZkZk9PpzNre3vYHAoFNGHqIn9yCfvyLAY66CyM9GRsbq8zOzn5TV1c3VFpa6pQyrcPDw+rOzs5L8/PzZ7a2tnyYkXc4XbPXbOxlIBd6pFQq05KTk11NTU22ysrKJWEf1dvbq21vb692u91JPp/vE05dh97/zQBHPoDpPp6RkbHQ09PTlZ6evikcoGZnZ1X19fW1a2trOizLGk6Vhc5EqIF46BlGfiotLc0+MDBwV6VS+YUwlNfrVZSXlzesrKxkYSa4HJehnYHJQ37XijXPTUpKcnd0dHSFC87itWw224OUlBQXGCaygt8FDWhxt9fyhmtubu42Go1eIczFpWxsbLSRQZa43L8NNPBR492+3xtOSvHaYLwmC/9aggZUfMnwObdYLINChAuMIbLw8QrZNHCebziNRrNYXFy8HmkDZJBFJtk0cI6vV4PBMC1Eqcgik2z+MfDdnp+fb4+WAbzG7WSSLRefAFlBQYEzWgawlzjFjUxLA9zhZJmZmd+iZYCbmGggUS7856IBD/fy5eXlQ9GCcvsmk2waWOI/4+PjqdEyQJZoYIkG5phkJiYmdNEyQBaZZNPAC8aoubk5U7QMkEUm2TQwwgzncDiyRkdH1ZGGk0EWmNzwRmiAH54yw1mt1ouRNkAGWWJo9QYfQysDJDLcWcaoSMH7+voymRPJEhOzoEDu4/Ezcz/W5fTU1JSxqKjorVqt3gonfHV1Nb6lpaUB6SgOBu7jVP/uRHQb6zKNAHkEGa6GMSpccF6rqqqq1uVyHSUDp+7sTkSCmNGqkdkcCJD6iooKM45xB4VjylVlZWVNmIETvDYZ0Pfg98ElCBaX4iWm6ILH49H29/cXIM/ZTSaTe79r3tbWZtnY2NCIsfwatCC1MelmgAw2JmazebCkpERSYBEbk4u8qcXGZFocuaTGJLQ1u8kAuUdrtog9fWNXa3YMrVlWuFqzP3ZPqB66ihlRSWxOaeyx+KjZD9odh7bnpRLb8+dS2/NfAgwAg5rkol00QQkAAAAASUVORK5CYII=) 0 17, default; 236 | cursor: -webkit-image-set(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGM0NFMkEyQTRCOTcxMUU3OTc2Mzg4ODUyQTFDQUVGNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGM0NFMkEyQjRCOTcxMUU3OTc2Mzg4ODUyQTFDQUVGNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkYzQ0UyQTI4NEI5NzExRTc5NzYzODg4NTJBMUNBRUY3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkYzQ0UyQTI5NEI5NzExRTc5NzYzODg4NTJBMUNBRUY3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+BtIaIAAAA8pJREFUeNrEl99LU2Ecxs9++IOcmdZkieg2CdmchWgWSCBpFIRk3kldCKLhdIpo/hMVQkujRNlFoHeVdqGQIlRQpijlDxRsmpJjymhtlem2eh45iyUSJ93WFx7PPBvn87zve855n69Mr9cLEisBKoGKoBwoAzosfvcF+gjNQK+gYeirlIvKJBjQQg1QuUKhSJTJZHIc5TKx+IOfYvn9/gAOARw9OP0Eugct7ddAHNQKxg25XB4fExOjSE1NXczJyZkuLCz8kJeXt67T6XZGabfbEyYnJ9VjY2P6mZkZk9PpzNre3vYHAoFNGHqIn9yCfvyLAY66CyM9GRsbq8zOzn5TV1c3VFpa6pQyrcPDw+rOzs5L8/PzZ7a2tnyYkXc4XbPXbOxlIBd6pFQq05KTk11NTU22ysrKJWEf1dvbq21vb692u91JPp/vE05dh97/zQBHPoDpPp6RkbHQ09PTlZ6evikcoGZnZ1X19fW1a2trOizLGk6Vhc5EqIF46BlGfiotLc0+MDBwV6VS+YUwlNfrVZSXlzesrKxkYSa4HJehnYHJQ37XijXPTUpKcnd0dHSFC87itWw224OUlBQXGCaygt8FDWhxt9fyhmtubu42Go1eIczFpWxsbLSRQZa43L8NNPBR492+3xtOSvHaYLwmC/9aggZUfMnwObdYLINChAuMIbLw8QrZNHCebziNRrNYXFy8HmkDZJBFJtk0cI6vV4PBMC1Eqcgik2z+MfDdnp+fb4+WAbzG7WSSLRefAFlBQYEzWgawlzjFjUxLA9zhZJmZmd+iZYCbmGggUS7856IBD/fy5eXlQ9GCcvsmk2waWOI/4+PjqdEyQJZoYIkG5phkJiYmdNEyQBaZZNPAC8aoubk5U7QMkEUm2TQwwgzncDiyRkdH1ZGGk0EWmNzwRmiAH54yw1mt1ouRNkAGWWJo9QYfQysDJDLcWcaoSMH7+voymRPJEhOzoEDu4/Ezcz/W5fTU1JSxqKjorVqt3gonfHV1Nb6lpaUB6SgOBu7jVP/uRHQb6zKNAHkEGa6GMSpccF6rqqqq1uVyHSUDp+7sTkSCmNGqkdkcCJD6iooKM45xB4VjylVlZWVNmIETvDYZ0Pfg98ElCBaX4iWm6ILH49H29/cXIM/ZTSaTe79r3tbWZtnY2NCIsfwatCC1MelmgAw2JmazebCkpERSYBEbk4u8qcXGZFocuaTGJLQ1u8kAuUdrtog9fWNXa3YMrVlWuFqzP3ZPqB66ihlRSWxOaeyx+KjZD9odh7bnpRLb8+dS2/NfAgwAg5rkol00QQkAAAAASUVORK5CYII=) 1x, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGM0NFMkEyQTRCOTcxMUU3OTc2Mzg4ODUyQTFDQUVGNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGM0NFMkEyQjRCOTcxMUU3OTc2Mzg4ODUyQTFDQUVGNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkYzQ0UyQTI4NEI5NzExRTc5NzYzODg4NTJBMUNBRUY3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkYzQ0UyQTI5NEI5NzExRTc5NzYzODg4NTJBMUNBRUY3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+BtIaIAAAA8pJREFUeNrEl99LU2Ecxs9++IOcmdZkieg2CdmchWgWSCBpFIRk3kldCKLhdIpo/hMVQkujRNlFoHeVdqGQIlRQpijlDxRsmpJjymhtlem2eh45iyUSJ93WFx7PPBvn87zve855n69Mr9cLEisBKoGKoBwoAzosfvcF+gjNQK+gYeirlIvKJBjQQg1QuUKhSJTJZHIc5TKx+IOfYvn9/gAOARw9OP0Eugct7ddAHNQKxg25XB4fExOjSE1NXczJyZkuLCz8kJeXt67T6XZGabfbEyYnJ9VjY2P6mZkZk9PpzNre3vYHAoFNGHqIn9yCfvyLAY66CyM9GRsbq8zOzn5TV1c3VFpa6pQyrcPDw+rOzs5L8/PzZ7a2tnyYkXc4XbPXbOxlIBd6pFQq05KTk11NTU22ysrKJWEf1dvbq21vb692u91JPp/vE05dh97/zQBHPoDpPp6RkbHQ09PTlZ6evikcoGZnZ1X19fW1a2trOizLGk6Vhc5EqIF46BlGfiotLc0+MDBwV6VS+YUwlNfrVZSXlzesrKxkYSa4HJehnYHJQ37XijXPTUpKcnd0dHSFC87itWw224OUlBQXGCaygt8FDWhxt9fyhmtubu42Go1eIczFpWxsbLSRQZa43L8NNPBR492+3xtOSvHaYLwmC/9aggZUfMnwObdYLINChAuMIbLw8QrZNHCebziNRrNYXFy8HmkDZJBFJtk0cI6vV4PBMC1Eqcgik2z+MfDdnp+fb4+WAbzG7WSSLRefAFlBQYEzWgawlzjFjUxLA9zhZJmZmd+iZYCbmGggUS7856IBD/fy5eXlQ9GCcvsmk2waWOI/4+PjqdEyQJZoYIkG5phkJiYmdNEyQBaZZNPAC8aoubk5U7QMkEUm2TQwwgzncDiyRkdH1ZGGk0EWmNzwRmiAH54yw1mt1ouRNkAGWWJo9QYfQysDJDLcWcaoSMH7+voymRPJEhOzoEDu4/Ezcz/W5fTU1JSxqKjorVqt3gonfHV1Nb6lpaUB6SgOBu7jVP/uRHQb6zKNAHkEGa6GMSpccF6rqqqq1uVyHSUDp+7sTkSCmNGqkdkcCJD6iooKM45xB4VjylVlZWVNmIETvDYZ0Pfg98ElCBaX4iWm6ILH49H29/cXIM/ZTSaTe79r3tbWZtnY2NCIsfwatCC1MelmgAw2JmazebCkpERSYBEbk4u8qcXGZFocuaTGJLQ1u8kAuUdrtog9fWNXa3YMrVlWuFqzP3ZPqB66ihlRSWxOaeyx+KjZD9odh7bnpRLb8+dS2/NfAgwAg5rkol00QQkAAAAASUVORK5CYII=) 2x) 0 17, default; } 237 | 238 | .canvas-cursor__crosshair { 239 | cursor: crosshair; } 240 | 241 | .canvas-cursor__font { 242 | cursor: text; } 243 | 244 | .canvas-cursor__default { 245 | cursor: default; } 246 | 247 | .canvas-tools_text_helper { 248 | display: none; 249 | position: absolute; 250 | border: 1px dashed #000; 251 | padding: 2px; 252 | outline: none; 253 | min-width: 16px; 254 | min-height: 16px; 255 | line-height: 16px; 256 | resize: none; 257 | overflow: hidden; 258 | background-color: transparent; } 259 | 260 | /*# sourceMappingURL=canvastools.css.map*/ -------------------------------------------------------------------------------- /dist/canvastools.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * CanvasTools v1.0.0 3 | * (github)https://github.com/S-mohan/canvasTools 4 | * (url) https://smohan.net/lab/canvastools 5 | * (c) smohan 6 | * license MIT 7 | */ 8 | (function webpackUniversalModuleDefinition(root, factory) { 9 | if(typeof exports === 'object' && typeof module === 'object') 10 | module.exports = factory(); 11 | else if(typeof define === 'function' && define.amd) 12 | define("CanvasTools", [], factory); 13 | else if(typeof exports === 'object') 14 | exports["CanvasTools"] = factory(); 15 | else 16 | root["CanvasTools"] = factory(); 17 | })(this, function() { 18 | return /******/ (function(modules) { // webpackBootstrap 19 | /******/ // The module cache 20 | /******/ var installedModules = {}; 21 | /******/ 22 | /******/ // The require function 23 | /******/ function __webpack_require__(moduleId) { 24 | /******/ 25 | /******/ // Check if module is in cache 26 | /******/ if(installedModules[moduleId]) { 27 | /******/ return installedModules[moduleId].exports; 28 | /******/ } 29 | /******/ // Create a new module (and put it into the cache) 30 | /******/ var module = installedModules[moduleId] = { 31 | /******/ i: moduleId, 32 | /******/ l: false, 33 | /******/ exports: {} 34 | /******/ }; 35 | /******/ 36 | /******/ // Execute the module function 37 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 38 | /******/ 39 | /******/ // Flag the module as loaded 40 | /******/ module.l = true; 41 | /******/ 42 | /******/ // Return the exports of the module 43 | /******/ return module.exports; 44 | /******/ } 45 | /******/ 46 | /******/ 47 | /******/ // expose the modules object (__webpack_modules__) 48 | /******/ __webpack_require__.m = modules; 49 | /******/ 50 | /******/ // expose the module cache 51 | /******/ __webpack_require__.c = installedModules; 52 | /******/ 53 | /******/ // identity function for calling harmony imports with the correct context 54 | /******/ __webpack_require__.i = function(value) { return value; }; 55 | /******/ 56 | /******/ // define getter function for harmony exports 57 | /******/ __webpack_require__.d = function(exports, name, getter) { 58 | /******/ if(!__webpack_require__.o(exports, name)) { 59 | /******/ Object.defineProperty(exports, name, { 60 | /******/ configurable: false, 61 | /******/ enumerable: true, 62 | /******/ get: getter 63 | /******/ }); 64 | /******/ } 65 | /******/ }; 66 | /******/ 67 | /******/ // getDefaultExport function for compatibility with non-harmony modules 68 | /******/ __webpack_require__.n = function(module) { 69 | /******/ var getter = module && module.__esModule ? 70 | /******/ function getDefault() { return module['default']; } : 71 | /******/ function getModuleExports() { return module; }; 72 | /******/ __webpack_require__.d(getter, 'a', getter); 73 | /******/ return getter; 74 | /******/ }; 75 | /******/ 76 | /******/ // Object.prototype.hasOwnProperty.call 77 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 78 | /******/ 79 | /******/ // __webpack_public_path__ 80 | /******/ __webpack_require__.p = ""; 81 | /******/ 82 | /******/ // Load entry module and return exports 83 | /******/ return __webpack_require__(__webpack_require__.s = 4); 84 | /******/ }) 85 | /************************************************************************/ 86 | /******/ ([ 87 | /* 0 */ 88 | /***/ (function(module, exports) { 89 | 90 | // removed by extract-text-webpack-plugin 91 | 92 | /***/ }), 93 | /* 1 */ 94 | /***/ (function(module, exports, __webpack_require__) { 95 | 96 | "use strict"; 97 | 98 | 99 | //Element.closet Polyfill 100 | //https://developer.mozilla.org/en-US/docs/Web/API/Element/closest 101 | if (window.Element && !Element.prototype.closest) { 102 | Element.prototype.closest = function (s) { 103 | var matches = (this.document || this.ownerDocument).querySelectorAll(s), 104 | i, 105 | el = this; 106 | do { 107 | i = matches.length; 108 | while (--i >= 0 && matches.item(i) !== el) {}; 109 | } while (i < 0 && (el = el.parentElement)); 110 | return el; 111 | }; 112 | } 113 | 114 | /***/ }), 115 | /* 2 */ 116 | /***/ (function(module, exports, __webpack_require__) { 117 | 118 | "use strict"; 119 | 120 | 121 | Object.defineProperty(exports, "__esModule", { 122 | value: true 123 | }); 124 | var ButtonsMap = { 125 | rect: { 126 | panel: 'stroke', 127 | name: '矩形工具' 128 | }, 129 | ellipse: { 130 | panel: 'stroke', 131 | name: '椭圆工具' 132 | }, 133 | brush: { 134 | panel: 'stroke', 135 | name: '画笔工具' 136 | }, 137 | arrow: { 138 | panel: 'stroke', 139 | name: '箭头工具' 140 | }, 141 | mosaic: { 142 | panel: 'mosaic', 143 | name: '马赛克工具' 144 | }, 145 | font: { 146 | panel: 'font', 147 | name: '文字工具' 148 | }, 149 | rubber: { 150 | name: '橡皮擦' 151 | }, 152 | undo: { 153 | name: '撤销操作' 154 | }, 155 | save: { 156 | name: '保存' 157 | } 158 | 159 | //可用颜色 160 | };var ColorList = ['#000000', '#808080', '#800000', '#f7883a', '#308430', '#385ad3', '#800080', '#009999', '#ffffff', '#c0c0c0', '#fb3838', '#ffff00', '#99cc00', '#3894e4', '#f31bf3', '#16dcdc']; 161 | 162 | //可选择字号,Chrome不支持小于12号的字体 163 | var FontSize = [12, 14, 16, 18, 20, 22]; 164 | 165 | //画笔大小 166 | var StrokeWidth = [2, 4, 6]; 167 | 168 | /** 169 | * 获取buttons模版 170 | * @param {Array} buttons [可用按钮] 171 | * @return {String} 172 | */ 173 | var getButtons = function getButtons() { 174 | var buttons = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 175 | 176 | var html = []; 177 | var useButton = function useButton(btn) { 178 | return buttons && !!~buttons.indexOf(btn); 179 | }; 180 | for (var key in ButtonsMap) { 181 | if (useButton(key)) { 182 | var btn = ButtonsMap[key]; 183 | html.push('
    \n\t\t\t\n\t\t\t
    '); 184 | } 185 | } 186 | return html.join(''); 187 | }; 188 | 189 | /** 190 | * 获取颜色面板 191 | * @param {String} color [当前选中色] 192 | * @return {String} 193 | */ 194 | var getColorPanel = function getColorPanel() { 195 | var color = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '#fb3838'; 196 | 197 | var html = ''; 198 | html += '
    '; 199 | html += ''; 200 | html += '
    '; 201 | 202 | var items1 = [], 203 | items2 = []; 204 | for (var i = 0; i < 16; i++) { 205 | var item = '
  • '; 206 | if (i < 8) { 207 | items1.push(item); 208 | } else { 209 | items2.push(item); 210 | } 211 | } 212 | 213 | html += '
      ' + items1.join('') + '
    '; 214 | html += '
      ' + items2.join('') + '
    '; 215 | 216 | html += '
    '; 217 | html += '
    '; 218 | 219 | return html; 220 | }; 221 | 222 | /** 223 | * 获取画笔大小面板 224 | * @param {Number} stroke [当前画笔大小] 225 | * @return {String} 226 | */ 227 | var getStrokePanel = function getStrokePanel() { 228 | var stroke = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2; 229 | 230 | var html = '
    '; 231 | for (var i = 0, len = StrokeWidth.length; i < len; i++) { 232 | var size = StrokeWidth[i]; 233 | var classes = ['stroke', 'js-stroke-width']; 234 | size === stroke && classes.push('active'); 235 | html += ''; 236 | } 237 | html += '
    '; 238 | return html; 239 | }; 240 | 241 | /** 242 | * 获取字号选择器 243 | * @param {Number} fontSize [默认字号] 244 | * @return {String} 245 | */ 246 | var getFontPanel = function getFontPanel() { 247 | var fontSize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 12; 248 | 249 | var html = ''; 256 | return html; 257 | }; 258 | 259 | /** 260 | * 获取模糊度模版 261 | * @param {Number} ambiguite [默认模糊度 0 - 1] 262 | * @return {String} 263 | */ 264 | var getAmbiguity = function getAmbiguity() { 265 | var ambiguite = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : .5; 266 | return ''; 267 | }; 268 | 269 | exports.default = { 270 | getButtons: getButtons, 271 | getColorPanel: getColorPanel, 272 | getStrokePanel: getStrokePanel, 273 | getFontPanel: getFontPanel, 274 | getAmbiguity: getAmbiguity 275 | }; 276 | module.exports = exports['default']; 277 | 278 | /***/ }), 279 | /* 3 */ 280 | /***/ (function(module, exports, __webpack_require__) { 281 | 282 | "use strict"; 283 | 284 | 285 | Object.defineProperty(exports, "__esModule", { 286 | value: true 287 | }); 288 | 289 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 290 | 291 | var ArrayProto = Array.prototype; 292 | 293 | var SelectorRegs = { 294 | id: /^#([\w-]+)$/, 295 | className: /^\.([\w-]+)$/, 296 | tagName: /^[\w-]+$/ 297 | }; 298 | 299 | var isObject = function isObject(obj) { 300 | return obj !== null && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object'; 301 | }; 302 | 303 | var isPlainObject = function isPlainObject(obj) { 304 | return Object.prototype.toString.call(obj) === '[object Object]'; 305 | }; 306 | 307 | /** 308 | * 获取元素对象集合 309 | * @param {String} selector [选择器] 310 | * @param {HTMLElement} context [上下文对象] 311 | * @return {Array} [元素节点集合] 312 | */ 313 | var $ = function $() { 314 | var selector = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '*'; 315 | var context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document; 316 | 317 | if (typeof selector === "string") { 318 | selector = selector.trim(); 319 | var dom = []; 320 | if (SelectorRegs.id.test(selector)) { 321 | dom = document.getElementById(RegExp.$1); 322 | dom = dom ? [dom] : []; 323 | } else if (SelectorRegs.className.test(selector)) { 324 | dom = context.getElementsByClassName(RegExp.$1); 325 | } else if (SelectorRegs.tagName.test(selector)) { 326 | dom = context.getElementsByTagName(selector); 327 | } else { 328 | dom = context.querySelectorAll(selector); 329 | } 330 | return ArrayProto.slice.call(dom); 331 | } 332 | return []; 333 | }; 334 | 335 | /** 336 | * 对象遍历 337 | * @param {Object | Array} object [对象源] 338 | * @param {Function} callback [回调] 339 | * @return 340 | */ 341 | var each = function each(object, callback) { 342 | if ((typeof object === 'undefined' ? 'undefined' : _typeof(object)) === "object" && typeof callback === "function") { 343 | if (Array.isArray(object)) { 344 | for (var i = 0, len = object.length; i < len; i++) { 345 | if (callback.call(object[i], i, object[i]) === false) { 346 | break; 347 | } 348 | } 349 | } else if ('length' in object && typeof object.length === "number") { 350 | //这地方不太严谨,谨慎使用 351 | for (var k in object) { 352 | if (callback.call(object[k], k, object[k]) === false) { 353 | break; 354 | } 355 | } 356 | } 357 | } 358 | }; 359 | 360 | /** 361 | * 事件绑定,支持代理 362 | * @param {HTMLElement} element [DOM元素] 363 | * @param {String} eventType [事件类型] 364 | * @param {String} selector [选择器] 365 | * @param {Function} callback [回调] 366 | * @return 367 | */ 368 | var bind = function bind(element, eventType, selector, callback) { 369 | var sel = void 0, 370 | handler = void 0; 371 | if (typeof selector === "function") { 372 | handler = selector; 373 | } else if (typeof selector === "string" && typeof callback === "function") { 374 | sel = selector; 375 | } else { 376 | return; 377 | } 378 | if (sel) { 379 | //事件代理 380 | handler = function handler(e) { 381 | var nodes = $(sel, element); 382 | var matched = false; 383 | for (var i = 0, len = nodes.length; i < len; i++) { 384 | var node = nodes[i]; 385 | if (node === e.target || node.contains(e.target)) { 386 | matched = node; 387 | break; 388 | } 389 | } 390 | if (matched) { 391 | callback.apply(matched, ArrayProto.slice.call(arguments)); 392 | } 393 | }; 394 | } 395 | 396 | element.addEventListener(eventType, handler, false); 397 | }; 398 | 399 | /** 400 | * 事件解绑 401 | * @param {HTMLElement} element [DOM元素] 402 | * @param {String} eventType [事件类型] 403 | * @param {Function} callback [回调] 404 | * @return 405 | */ 406 | var unbind = function unbind(element, eventType, callback) { 407 | return element.removeEventListener(eventType, callback, false 408 | 409 | /** 410 | * 获取指定元素样式 411 | * @param {HTMLElement} element 412 | * @return {Object} 413 | */ 414 | ); 415 | };var getComputedStyles = function getComputedStyles(element) { 416 | return element.ownerDocument.defaultView.getComputedStyle(element, null 417 | 418 | /** 419 | * 对象深度拷贝 420 | * @param {Object} out 421 | * @return {Object} 422 | */ 423 | ); 424 | };var extend = function extend() { 425 | var out = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 426 | 427 | for (var i = 1, len = arguments.length; i < len; i++) { 428 | var obj = arguments[i]; 429 | if (!obj || !Object.keys(obj).length) continue; 430 | for (var key in obj) { 431 | if (obj.hasOwnProperty(key)) { 432 | if (isPlainObject(obj[key])) out[key] = extend(out[key], obj[key]);else out[key] = obj[key]; 433 | } 434 | } 435 | } 436 | return out; 437 | }; 438 | 439 | var $on = function $on(elements, eventType, selector, callback) { 440 | if (!Array.isArray(elements)) { 441 | elements = [elements]; 442 | } 443 | each(elements, function (index, element) { 444 | return bind(element, eventType, selector, callback); 445 | }); 446 | }; 447 | 448 | var $off = function $off(elements, eventType, callback) { 449 | if (!Array.isArray(elements)) { 450 | elements = [elements]; 451 | } 452 | each(elements, function (index, element) { 453 | return unbind(element, eventType, callback); 454 | }); 455 | }; 456 | 457 | var classList = function classList(elements) { 458 | var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'add'; 459 | var classes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; 460 | 461 | if (!Array.isArray(elements)) { 462 | elements = [elements]; 463 | } 464 | each(elements, function (index, element) { 465 | return element.classList[type](classes); 466 | }); 467 | }; 468 | 469 | exports.default = { 470 | $: $, 471 | each: each, 472 | bind: bind, 473 | extend: extend, 474 | unbind: unbind, 475 | getComputedStyles: getComputedStyles, 476 | classList: classList, 477 | $on: $on, 478 | $off: $off 479 | }; 480 | module.exports = exports['default']; 481 | 482 | /***/ }), 483 | /* 4 */ 484 | /***/ (function(module, exports, __webpack_require__) { 485 | 486 | "use strict"; 487 | 488 | 489 | Object.defineProperty(exports, "__esModule", { 490 | value: true 491 | }); 492 | 493 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 494 | 495 | __webpack_require__(0); 496 | 497 | __webpack_require__(1); 498 | 499 | var _utils = __webpack_require__(3); 500 | 501 | var _utils2 = _interopRequireDefault(_utils); 502 | 503 | var _template = __webpack_require__(2); 504 | 505 | var _template2 = _interopRequireDefault(_template); 506 | 507 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 508 | 509 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 510 | 511 | //stroke类型操作 512 | var STROKE_TYPES = ['rect', 'ellipse', 'brush', 'arrow', 'mosaic', 'font', 'rubber']; 513 | 514 | //默认颜色 515 | var STROKE_DEFAULT_COLOR = '#fb3838'; 516 | 517 | //默认画笔大小 518 | var STROKE_DEFAULT_WIDTH = 2; 519 | 520 | //辅助输入框padding值 521 | var TEXT_HELPER_PADDING = 2; 522 | 523 | //辅助输入框层级 524 | var TEXT_HELPER_ZINDEX = 1990; 525 | 526 | //辅助输入框ID 527 | var TEXT_HELPER_ID = 'canvas-tools_text_helper'; 528 | 529 | //输入框默认字体大小 530 | //以设置的字体大小为准,改值仅做辅助值 531 | var TEXT_HELPER_FONT_SIZE = 12; 532 | 533 | //字体 534 | var TEXT_FONT_FAMILY = '"Helvetica Neue",Helvetica,Arial,"Hiragino Sans GB","Hiragino Sans GB W3","WenQuanYi Micro Hei",sans-serif'; 535 | 536 | //马赛克模糊度 537 | var AMBIGUITY_LEVEL = .7; 538 | 539 | /** 540 | * 创建画笔+颜色容器 541 | * @param {Number} stroke [默认画笔] 542 | * @param {String} color [默认颜色] 543 | * @return {HTMLElement} 544 | */ 545 | var buildStrokePanel = function buildStrokePanel() { 546 | var stroke = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : STROKE_DEFAULT_COLOR; 547 | var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : STROKE_DEFAULT_COLOR; 548 | 549 | var el = document.createElement('div'); 550 | el.className = 'canvas-tools__panel js-panel__stroke'; 551 | el.style.cssText += 'white-space: nowrap!important;'; 552 | el.innerHTML = _template2.default.getStrokePanel(stroke) + _template2.default.getColorPanel(color); 553 | return el; 554 | }; 555 | 556 | /** 557 | * 创建字号+颜色容器 558 | * @param {Number} fontSize [默认字号] 559 | * @param {String} color [默认颜色] 560 | * @return {HTMLElement} 561 | */ 562 | var buildFontPanel = function buildFontPanel() { 563 | var fontSize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : TEXT_HELPER_FONT_SIZE; 564 | var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : STROKE_DEFAULT_COLOR; 565 | 566 | var el = document.createElement('div'); 567 | el.className = 'canvas-tools__panel js-panel__font'; 568 | el.style.cssText += 'white-space: nowrap!important;'; 569 | el.innerHTML = _template2.default.getFontPanel(fontSize) + _template2.default.getColorPanel(color); 570 | return el; 571 | }; 572 | 573 | /** 574 | * 创建画笔 + 模糊度容器 575 | * @param {Number} stroke [默认画笔] 576 | * @param {Number} ambiguity [默认模糊度] 577 | * @return {HTMLElement} 578 | */ 579 | var buildAmbiguityPanel = function buildAmbiguityPanel() { 580 | var stroke = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : STROKE_DEFAULT_COLOR; 581 | var ambiguity = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : AMBIGUITY_LEVEL; 582 | 583 | var el = document.createElement('div'); 584 | el.className = 'canvas-tools__panel js-panel__mosaic'; 585 | el.style.cssText += 'white-space: nowrap!important;'; 586 | el.innerHTML = _template2.default.getStrokePanel(stroke) + _template2.default.getAmbiguity(ambiguity); 587 | return el; 588 | }; 589 | 590 | /** 591 | * 创建辅助文本输入框 592 | * @return {HTMLElement} 593 | */ 594 | var getTextHelper = function getTextHelper() { 595 | var $textHelper = document.getElementById(TEXT_HELPER_ID); 596 | if (!$textHelper) { 597 | $textHelper = document.createElement('div'); 598 | $textHelper.setAttribute('contenteditable', 'plaintext-only'); 599 | $textHelper.setAttribute('spellcheck', 'false'); 600 | $textHelper.setAttribute('id', TEXT_HELPER_ID); 601 | $textHelper.className = TEXT_HELPER_ID; 602 | document.body.appendChild($textHelper); 603 | } 604 | $textHelper.innerHTML = ''; 605 | $textHelper.style.cssText = 'display: block'; 606 | return $textHelper; 607 | }; 608 | 609 | /** 610 | * 初始化辅助文本输入框样式 611 | * @param {MouseEvent} event [鼠标事件] 612 | * @param {Object} state [instance state] 613 | * @param {Object} rect [instance rect] 614 | * @return {HTMLElement} 615 | */ 616 | var insertTextHelper = function insertTextHelper(event, state, rect) { 617 | var $textHelper = getTextHelper(), 618 | threshold = state.fontSize || TEXT_HELPER_FONT_SIZE, 619 | padding = 2 * TEXT_HELPER_PADDING + 2, 620 | pos = getPos(event, rect); 621 | 622 | var x = event.pageX, 623 | y = event.pageY, 624 | maxW = Math.floor(rect.width - pos.x) - padding, 625 | maxH = Math.floor(rect.height - pos.y) - padding; 626 | 627 | if (maxW <= threshold) { 628 | x -= threshold + padding - maxW; 629 | maxW = threshold; 630 | } 631 | 632 | if (maxH <= threshold) { 633 | y -= threshold + padding - maxH; 634 | maxH = threshold; 635 | } 636 | 637 | if (maxW >= rect.width) { 638 | maxW = rect.width - pos.x; 639 | } 640 | 641 | if (maxH >= rect.height) { 642 | maxH = rect.height - pos.y; 643 | } 644 | 645 | var styleMap = { 646 | 'font-size': threshold + 'px', 647 | 'line-height': threshold + 'px', 648 | 'min-width': threshold + 'px', 649 | 'min-height': threshold + 'px', 650 | 'max-width': maxW + 'px', 651 | 'max-height': maxH + 'px', 652 | 'z-index': TEXT_HELPER_ZINDEX, 653 | 'font-family': TEXT_FONT_FAMILY, 654 | display: 'block', 655 | position: 'absolute', 656 | top: y + 'px', 657 | left: x + 'px', 658 | color: state.strokeColor, 659 | padding: TEXT_HELPER_PADDING + 'px', 660 | overflow: 'hidden' 661 | }; 662 | 663 | var style = ''; 664 | 665 | for (var key in styleMap) { 666 | style += key + ':' + styleMap[key] + ';'; 667 | } 668 | 669 | $textHelper.style.cssText = style; 670 | $textHelper.focus(); 671 | return $textHelper; 672 | }; 673 | 674 | /** 675 | * 移除辅助文本输入框 676 | * @return 677 | */ 678 | var removeTextHelper = function removeTextHelper() { 679 | var $textHelper = document.getElementById(TEXT_HELPER_ID); 680 | if (!$textHelper) { 681 | return; 682 | } 683 | $textHelper.innerHTML = ''; 684 | $textHelper.style.cssText = 'display: none'; 685 | }; 686 | 687 | /** 688 | * 获取鼠标在Canvas上的位置 689 | * @param {Element Event} event [鼠标事件] 690 | * @param {Object} rect [Canvas rect] 691 | * @return {Object} 692 | */ 693 | var getPos = function getPos(event, rect) { 694 | var x = event.pageX - rect.left; 695 | var y = event.pageY - rect.top; 696 | if (x <= 0) x = 0; 697 | if (x >= rect.width) x = rect.width; 698 | if (y <= 0) y = 0; 699 | if (y >= rect.height) y = rect.height; 700 | 701 | return { 702 | x: x, 703 | y: y 704 | }; 705 | }; 706 | 707 | /** 708 | * 默认配置 709 | * @type {Object} 710 | */ 711 | var defaults = { 712 | //工具条父级对象容器 713 | container: document.body, 714 | //显示按钮 715 | buttons: ['rect', 'ellipse', 'brush', 'font', 'mosaic', 'undo', 'save'] 716 | 717 | //创建一个下载链接 718 | };var $saveLink = document.createElementNS('http://www.w3.org/1999/xhtml', 'a' 719 | 720 | //是否支持原生下载 721 | );var canUseSaveLink = 'download' in $saveLink; 722 | 723 | //下载文件 724 | var __downloadFile = function __downloadFile() { 725 | var fileName = 'canvas_' + Date.now() + '.png'; 726 | var canvas = this.canvas; 727 | 728 | if (canUseSaveLink) { 729 | var fileUrl = canvas.toDataURL('png'); 730 | fileUrl = fileUrl.replace('image/png', 'image/octet-stream'); 731 | setTimeout(function () { 732 | $saveLink.href = fileUrl; 733 | $saveLink.download = fileName; 734 | 735 | //触发click事件 736 | $saveLink.dispatchEvent(new MouseEvent('click')); 737 | }); 738 | } 739 | 740 | //for ie 10+ 741 | else if (typeof navigator !== "undefined" && typeof canvas.msToBlob === 'function' && navigator.msSaveBlob) { 742 | navigator.msSaveBlob(canvas.msToBlob(), fileName); 743 | } 744 | 745 | // other 746 | else { 747 | console.log('您的浏览器不支持该操作'); 748 | } 749 | }; 750 | 751 | //相关事件绑定 752 | function __bindEvents() { 753 | var self = this; 754 | var canvas = this.canvas, 755 | context = this.context, 756 | $el = this.$el, 757 | state = this.state, 758 | config = this.config, 759 | rect = this.rect, 760 | _handles = this._handles, 761 | history = this.history; 762 | 763 | 764 | var $btns = _utils2.default.$('.js-btn', $el), 765 | $fontPanel = _utils2.default.$('.js-panel__font', $el)[0], 766 | $strokePanel = _utils2.default.$('.js-panel__stroke', $el)[0], 767 | $mosaicPanel = _utils2.default.$('.js-panel__mosaic', $el)[0], 768 | $colorSelected = _utils2.default.$('.js-color-selected', $el), 769 | $colors = _utils2.default.$('.js-color', $el), 770 | $strokeWidth = _utils2.default.$('.js-stroke-width', $el), 771 | $fontSize = _utils2.default.$('.js-font-size', $el), 772 | $mosaicAmbiguity = _utils2.default.$('.js-mosaic-ambiguity', $el 773 | 774 | //按钮事件 775 | );_handles.btnEmit = function (event) { 776 | var _this = this; 777 | 778 | event.stopPropagation(); 779 | if (state.drawType === 'font') { 780 | __drawFont.call(self, event); 781 | } 782 | var panel = this.getAttribute('data-panel'); 783 | var value = this.getAttribute('data-value'); 784 | _utils2.default.each($btns, function (index, $btn) { 785 | if ($btn !== _this) { 786 | _utils2.default.classList($btn, 'remove', 'active'); 787 | } 788 | }); 789 | if (!!~STROKE_TYPES.indexOf(value)) { 790 | state.drawType = value; 791 | } 792 | if (panel) { 793 | _utils2.default.classList(this, 'toggle', 'active'); 794 | var isActive = /active/.test(this.className); 795 | var visible = isActive ? 'block' : 'none'; 796 | if (panel === 'stroke') { 797 | $fontPanel.style.display = 'none'; 798 | $mosaicPanel.style.display = 'none'; 799 | $strokePanel.style.display = visible; 800 | } else if (panel === 'font') { 801 | $fontPanel.style.display = visible; 802 | $strokePanel.style.display = 'none'; 803 | $mosaicPanel.style.display = 'none'; 804 | } else if (panel === 'mosaic') { 805 | $mosaicPanel.style.display = visible; 806 | $fontPanel.style.display = 'none'; 807 | $strokePanel.style.display = 'none'; 808 | } 809 | } else { 810 | //$fontPanel.style.display = 'none' 811 | //$strokePanel.style.display = 'none' 812 | } 813 | 814 | if (value === 'save') { 815 | __downloadFile.call(self); 816 | return; 817 | } 818 | 819 | //history[0]是画布的初始状态 820 | //因此只有多于1个历史记录时才可以恢复上一步 821 | if (value === 'undo' && history.length > 1) { 822 | history.pop(); 823 | context.putImageData(history[history.length - 1], 0, 0, 0, 0, rect.width, rect.height); 824 | } 825 | 826 | __toggleCanvasCursor.call(self); 827 | }; 828 | 829 | _handles.toggleColor = function (event) { 830 | var color = this.getAttribute('data-value'); 831 | state.strokeColor = color; 832 | _utils2.default.each($colorSelected, function (index, item) { 833 | return item.style.background = color; 834 | }); 835 | }; 836 | 837 | _handles.toggleStrokeWidth = function (event) { 838 | state.strokeWidth = Number(this.getAttribute('data-value')); 839 | _utils2.default.each($strokeWidth, function (index, item) { 840 | var value = Number(item.getAttribute('data-value')); 841 | var method = value === state.strokeWidth ? 'add' : 'remove'; 842 | _utils2.default.classList(item, method, 'active'); 843 | }); 844 | }; 845 | 846 | _handles.toggleFontSize = function (event) { 847 | state.fontSize = Number(this.value); 848 | }; 849 | 850 | //鼠标在画布上的初始位置 851 | var _startPos = void 0; 852 | 853 | _handles.onMouseDown = function (event) { 854 | if (!!~STROKE_TYPES.indexOf(state.drawType) === false || state.drawType === 'font') { 855 | return; 856 | } 857 | _startPos = getPos(event, rect 858 | 859 | //保存当前快照 860 | );state.lastImageData = context.getImageData(0, 0, rect.width, rect.height 861 | 862 | //初始化context状态 863 | );context.lineCap = 'round'; 864 | context.lineJoin = 'round'; 865 | context.shadowBlur = 0; 866 | context.strokeStyle = state.strokeColor; 867 | context.lineWidth = state.strokeWidth; 868 | switch (state.drawType) { 869 | case 'rect': 870 | __drawRect.call(self, event, _startPos); 871 | break; 872 | case 'ellipse': 873 | __drawEllipse.call(self, event, _startPos); 874 | break; 875 | case 'mosaic': 876 | __drawMoasic.call(self, event); 877 | break; 878 | case 'brush': 879 | default: 880 | __drawBrush.call(self, event, _startPos); 881 | break; 882 | } 883 | 884 | _utils2.default.$on(document, 'mousemove', _handles.onMouseMove); 885 | _utils2.default.$on(document, 'mouseup', _handles.onMouseUp); 886 | }; 887 | 888 | _handles.onMouseMove = function (event) { 889 | if (!!~STROKE_TYPES.indexOf(state.drawType) === false || state.drawType === 'font') { 890 | return; 891 | } 892 | switch (state.drawType) { 893 | case 'rect': 894 | __drawRect.call(self, event, _startPos); 895 | break; 896 | case 'ellipse': 897 | __drawEllipse.call(self, event, _startPos); 898 | break; 899 | case 'mosaic': 900 | __drawMoasic.call(self, event); 901 | break; 902 | case 'brush': 903 | default: 904 | __drawBrush.call(self, event, null); 905 | break; 906 | } 907 | }; 908 | 909 | _handles.onMouseUp = function (event) { 910 | _utils2.default.$off(document, 'mousemove', _handles.onMouseMove); 911 | _utils2.default.$off(document, 'mouseup', _handles.onMouseUp); 912 | if (!!~STROKE_TYPES.indexOf(state.drawType) && state.drawType !== 'font') { 913 | __pushHistory.call(self); 914 | } 915 | }; 916 | 917 | _handles.insertTextHelper = function (event) { 918 | event.stopPropagation(); 919 | if (state.drawType !== 'font') { 920 | return; 921 | } 922 | if (state.isEntry) { 923 | __drawFont.call(self, event); 924 | return; 925 | } 926 | insertTextHelper(event, state, rect); 927 | state.isEntry = true; 928 | }; 929 | 930 | _handles.removeTextHelper = function (event) { 931 | if (state.drawType !== 'font') { 932 | removeTextHelper(); 933 | } else if (!event.target.closest('#canvas-tools-input')) { 934 | __drawFont.call(self, event); 935 | } 936 | }; 937 | 938 | _handles.resize = function (event) { 939 | var _rect = canvas.getBoundingClientRect(); 940 | rect.width = canvas.width; 941 | rect.height = canvas.height; 942 | rect.offsetWidth = canvas.offsetWidth; 943 | rect.offsetHeight = canvas.offsetHeight; 944 | rect.top = _rect.top; 945 | rect.left = _rect.left; 946 | state.drawType === 'font' && state.isEntry && __drawFont.call(self, event); 947 | }; 948 | 949 | _handles.toggleAmbiguity = function (event) { 950 | state.ambiguity = this.value; 951 | console.log(state); 952 | }; 953 | 954 | //按钮事件 955 | _utils2.default.$on($btns, 'click', _handles.btnEmit 956 | 957 | //切换颜色 958 | );_utils2.default.$on($colors, 'click', _handles.toggleColor 959 | 960 | //切换画笔大小 961 | );_utils2.default.$on($strokeWidth, 'click', _handles.toggleStrokeWidth 962 | 963 | //切换字体大小 964 | );_utils2.default.$on($fontSize, 'change', _handles.toggleFontSize); 965 | 966 | _utils2.default.$on($mosaicAmbiguity, 'change', _handles.toggleAmbiguity 967 | 968 | //矩形,椭圆,画笔等绘制 969 | );_utils2.default.$on(canvas, 'mousedown', _handles.onMouseDown 970 | 971 | //插入文本辅助框 972 | );_utils2.default.$on(canvas, 'click', _handles.insertTextHelper 973 | 974 | //移除文本辅助框 975 | );_utils2.default.$on(document, 'click', _handles.removeTextHelper 976 | 977 | //window resize 978 | );window.addEventListener('resize', _handles.resize); 979 | } 980 | 981 | /** 982 | * 绘制矩形 983 | * @param {MouseEvent} event [鼠标事件] 984 | * @param {Object} start [起始位置] 985 | * @return 986 | */ 987 | function __drawRect(event, start) { 988 | var context = this.context, 989 | rect = this.rect, 990 | state = this.state; 991 | 992 | var pos = getPos(event, rect); 993 | var width = pos.x - start.x; 994 | var height = pos.y - start.y; 995 | context.clearRect(0, 0, rect.width, rect.height); 996 | context.putImageData(state.lastImageData, 0, 0, 0, 0, rect.width, rect.height); 997 | context.save(); 998 | context.beginPath(); 999 | context.strokeRect(start.x, start.y, width, height); 1000 | context.restore(); 1001 | context.closePath(); 1002 | } 1003 | 1004 | /** 1005 | * 绘制椭圆 1006 | * @param {MouseEvent} event [鼠标事件] 1007 | * @param {Object} start [起始位置] 1008 | * @return 1009 | */ 1010 | function __drawEllipse(event, start) { 1011 | var context = this.context, 1012 | rect = this.rect, 1013 | state = this.state; 1014 | 1015 | var pos = getPos(event, rect); 1016 | var scaleX = 1 * ((pos.x - start.x) / 2); 1017 | var scaleY = 1 * ((pos.y - start.y) / 2); 1018 | var x = start.x / scaleX + 1; 1019 | var y = start.y / scaleY + 1; 1020 | context.clearRect(0, 0, rect.width, rect.height); 1021 | context.putImageData(state.lastImageData, 0, 0, 0, 0, rect.width, rect.height); 1022 | context.save(); 1023 | context.beginPath(); 1024 | context.scale(scaleX, scaleY); 1025 | context.arc(x, y, 1, 0, 2 * Math.PI); 1026 | context.restore(); 1027 | context.closePath(); 1028 | context.stroke(); 1029 | } 1030 | 1031 | /** 1032 | * 画笔工具自由绘制 1033 | * @param {MouseEvent} event [鼠标事件] 1034 | * @param {Object | null} start [起始位置] 1035 | * @return 1036 | */ 1037 | function __drawBrush(event) { 1038 | var start = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 1039 | var context = this.context, 1040 | rect = this.rect; 1041 | 1042 | if (start) { 1043 | context.beginPath(); 1044 | context.moveTo(start.x, start.y); 1045 | } else { 1046 | var pos = getPos(event, rect); 1047 | context.lineTo(pos.x, pos.y); 1048 | context.stroke(); 1049 | } 1050 | } 1051 | 1052 | /** 1053 | * 绘制文字 1054 | * @param {MouseEvent} event [鼠标事件] 1055 | * @return {[type]} 1056 | */ 1057 | function __drawFont(event) { 1058 | var $textHelper = document.getElementById(TEXT_HELPER_ID); 1059 | if (!$textHelper) { 1060 | this.state.isEntry = false; 1061 | return; 1062 | } 1063 | var content = $textHelper.textContent.trim(); 1064 | var length = content.length; 1065 | if (!content || !length) { 1066 | this.state.isEntry = false; 1067 | removeTextHelper(); 1068 | return; 1069 | } 1070 | var style = _utils2.default.getComputedStyles($textHelper), 1071 | threshold = this.state.fontSize || TEXT_HELPER_FONT_SIZE, 1072 | padding = 2 * TEXT_HELPER_PADDING, 1073 | context = this.context; 1074 | 1075 | var x = parseFloat(style.left) - this.rect.left + padding - 2, 1076 | y = parseFloat(style.top) - this.rect.top + threshold, 1077 | lineWidth = 0, 1078 | lastSubStrIndex = 0; 1079 | 1080 | context.beginPath(); 1081 | context.save(); 1082 | context.fillStyle = this.state.strokeColor; 1083 | context.font = this.state.fontSize + 'px ' + TEXT_FONT_FAMILY; 1084 | 1085 | for (var i = 0; i < length; i++) { 1086 | var char = content[i]; 1087 | 1088 | //让文字自动换行 1089 | lineWidth += context.measureText(char).width; 1090 | if (lineWidth > this.rect.width - x) { 1091 | context.fillText(content.substring(lastSubStrIndex, i), x, y); 1092 | y += threshold; 1093 | lineWidth = 0; 1094 | lastSubStrIndex = i; 1095 | } 1096 | if (i == length - 1) { 1097 | context.fillText(content.substring(lastSubStrIndex, i + 1), x, y); 1098 | } 1099 | } 1100 | context.restore(); 1101 | context.closePath(); 1102 | this.state.isEntry = false; 1103 | removeTextHelper(); 1104 | __pushHistory.call(this); 1105 | } 1106 | 1107 | /** 1108 | * 绘制马赛克 1109 | * @param {MouseEvent} event [鼠标事件] 1110 | * @return {[type]} 1111 | */ 1112 | function __drawMoasic(event) { 1113 | var context = this.context, 1114 | state = this.state, 1115 | rect = this.rect; 1116 | 1117 | var pos = getPos(event, rect); 1118 | var size = state.strokeWidth * 3; 1119 | 1120 | //获取当前位置1PX的颜色值 1121 | var data = context.getImageData(pos.x, pos.y, size, size).data; 1122 | 1123 | var r = 0, 1124 | g = 0, 1125 | b = 0; 1126 | 1127 | for (var row = 0; row < size; row++) { 1128 | for (var col = 0; col < size; col++) { 1129 | r += data[(size * row + col) * 4]; 1130 | g += data[(size * row + col) * 4 + 1]; 1131 | b += data[(size * row + col) * 4 + 2]; 1132 | } 1133 | } 1134 | 1135 | r = Math.round(r / (size * size)); 1136 | g = Math.round(g / (size * size)); 1137 | b = Math.round(b / (size * size)); 1138 | var color = 'rgba(' + r + ', ' + g + ', ' + b + ', ' + state.ambiguity + ')'; 1139 | context.beginPath(); 1140 | context.save(); 1141 | context.fillStyle = color; 1142 | context.fillRect(pos.x, pos.y, size, size); 1143 | context.restore(); 1144 | } 1145 | 1146 | /** 1147 | * 切换鼠标指针 1148 | * @return 1149 | */ 1150 | function __toggleCanvasCursor() { 1151 | var canvas = this.canvas; 1152 | var cursor = void 0; 1153 | switch (this.state.drawType) { 1154 | case 'brush': 1155 | cursor = 'brush'; 1156 | break; 1157 | case 'font': 1158 | cursor = 'font'; 1159 | break; 1160 | case 'mosaic': 1161 | cursor = 'mosaic'; 1162 | break; 1163 | case 'rect': 1164 | case 'ellipse': 1165 | cursor = 'crosshair'; 1166 | break; 1167 | default: 1168 | cursor = 'default'; 1169 | } 1170 | if (cursor) { 1171 | canvas.className = canvas.className.replace(/canvas-cursor__(\w+)/, '').trim() + (' canvas-cursor__' + cursor); 1172 | } 1173 | } 1174 | 1175 | /** 1176 | * 保存到历史记录 1177 | * @return 1178 | */ 1179 | function __pushHistory() { 1180 | this.history.push(this.context.getImageData(0, 0, this.rect.width, this.rect.height)); 1181 | } 1182 | 1183 | /** 1184 | * CanvasTools 1185 | * Class 1186 | */ 1187 | 1188 | var CanvasTools = function () { 1189 | 1190 | /** 1191 | * constructor 1192 | * @param {CanvasElement} canvas [canvas element object] 1193 | * @param {Object} options [config] 1194 | * @return {Object} [instance] 1195 | */ 1196 | function CanvasTools(canvas, options) { 1197 | _classCallCheck(this, CanvasTools); 1198 | 1199 | if (!canvas || typeof canvas.getContext !== 'function') { 1200 | throw new Error('invalid canvas object'); 1201 | } 1202 | this.canvas = canvas; 1203 | this.context = canvas.getContext('2d'); 1204 | this.config = _utils2.default.extend({}, defaults, options || {}); 1205 | 1206 | this.history = []; 1207 | this.state = Object.create(null); 1208 | this.rect = Object.create(null); 1209 | this._handles = Object.create(null); 1210 | 1211 | this.state.strokeWidth = STROKE_DEFAULT_WIDTH; 1212 | this.state.fontSize = TEXT_HELPER_FONT_SIZE; 1213 | this.state.strokeColor = STROKE_DEFAULT_COLOR; 1214 | this.state.ambiguity = AMBIGUITY_LEVEL; 1215 | this.state.drawType = 'brush'; 1216 | this.state.isEntry = false; 1217 | 1218 | this.rect.width = canvas.width; 1219 | this.rect.height = canvas.height; 1220 | 1221 | this.rect.offsetWidth = canvas.offsetWidth; 1222 | this.rect.offsetHeight = canvas.offsetHeight; 1223 | 1224 | var rect = canvas.getBoundingClientRect(); 1225 | this.rect.top = rect.top; 1226 | this.rect.left = rect.left; 1227 | 1228 | //保存现场 1229 | this.state.lastImageData = this.context.getImageData(0, 0, this.rect.width, this.rect.height 1230 | 1231 | //将画布的初始状态保存到历史记录 1232 | );__pushHistory.call(this); 1233 | 1234 | __toggleCanvasCursor.call(this); 1235 | 1236 | this.render(); 1237 | } 1238 | 1239 | /** 1240 | * 初始化工具条到DOM 1241 | * @return 1242 | */ 1243 | 1244 | 1245 | _createClass(CanvasTools, [{ 1246 | key: 'render', 1247 | value: function render() { 1248 | var C = this.config; 1249 | var S = this.state; 1250 | var el = document.createElement('div'); 1251 | el.className = 'canvas-tools'; 1252 | el.innerHTML = _template2.default.getButtons(C.buttons); 1253 | C.container.appendChild(el); 1254 | 1255 | this.$el = el; 1256 | this.$el.appendChild(buildStrokePanel(S.strokeWidth, S.strokeColor)); 1257 | this.$el.appendChild(buildFontPanel(S.fontSize, S.strokeColor)); 1258 | this.$el.appendChild(buildAmbiguityPanel(S.strokeWidth, S.ambiguity)); 1259 | __bindEvents.call(this); 1260 | } 1261 | }, { 1262 | key: 'refresh', 1263 | value: function refresh() {} 1264 | 1265 | /** 1266 | * destory 1267 | * @return 1268 | */ 1269 | 1270 | }, { 1271 | key: 'destory', 1272 | value: function destory() { 1273 | var canvas = this.canvas, 1274 | $el = this.$el, 1275 | _handles = this._handles; 1276 | 1277 | 1278 | var $btns = _utils2.default.$('.js-btn', $el), 1279 | $colors = _utils2.default.$('.js-color', $el), 1280 | $strokeWidth = _utils2.default.$('.js-stroke-width', $el), 1281 | $fontSize = _utils2.default.$('.js-font-size', $el), 1282 | $mosaicAmbiguity = _utils2.default.$('.js-mosaic-ambiguity', $el), 1283 | $textHelper = document.getElementById(TEXT_HELPER_ID); 1284 | 1285 | _utils2.default.$off($btns, 'click', _handles.btnEmit); 1286 | _utils2.default.$off($colors, 'click', _handles.toggleColor); 1287 | _utils2.default.$off($strokeWidth, 'click', _handles.toggleStrokeWidth); 1288 | _utils2.default.$off($fontSize, 'change', _handles.toggleFontSize); 1289 | _utils2.default.$off($mosaicAmbiguity, 'change', _handles.toggleAmbiguity); 1290 | _utils2.default.$off(canvas, 'mousedown', _handles.onMouseDown); 1291 | _utils2.default.$off(canvas, 'click', _handles.insertTextHelper); 1292 | _utils2.default.$off(document, 'click', _handles.removeTextHelper); 1293 | window.removeEventListener('resize', _handles.resize); 1294 | $textHelper && $textHelper.parentNoed.removeChild($textHelper); 1295 | this.canvas = null; 1296 | this.context = null; 1297 | this.history.length = 0; 1298 | this.config.container.removeChild(this.$el); 1299 | } 1300 | }]); 1301 | 1302 | return CanvasTools; 1303 | }(); 1304 | 1305 | exports.default = CanvasTools; 1306 | module.exports = exports['default']; 1307 | 1308 | /***/ }) 1309 | /******/ ]); 1310 | }); 1311 | //# sourceMappingURL=canvastools.js.map -------------------------------------------------------------------------------- /dist/canvastools.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///webpack/universalModuleDefinition","webpack:///webpack/bootstrap 07d30412e865d55647ad","webpack:///./src/scss/canvastools.scss","webpack:///./src/js/closet.js","webpack:///./src/js/template.js","webpack:///./src/js/utils.js","webpack:///./src/js/main.js"],"names":["window","Element","prototype","closest","s","matches","document","ownerDocument","querySelectorAll","i","el","length","item","parentElement","ButtonsMap","rect","panel","name","ellipse","brush","arrow","mosaic","font","rubber","undo","save","ColorList","FontSize","StrokeWidth","getButtons","buttons","html","useButton","indexOf","btn","key","push","join","getColorPanel","color","items1","items2","getStrokePanel","stroke","len","size","classes","getFontPanel","fontSize","selected","getAmbiguity","ambiguite","ArrayProto","Array","SelectorRegs","id","className","tagName","isObject","obj","isPlainObject","Object","toString","call","$","selector","context","trim","dom","test","getElementById","RegExp","$1","getElementsByClassName","getElementsByTagName","slice","each","object","callback","isArray","k","bind","element","eventType","sel","handler","e","nodes","matched","node","target","contains","apply","arguments","addEventListener","unbind","removeEventListener","getComputedStyles","defaultView","getComputedStyle","extend","out","keys","hasOwnProperty","$on","elements","index","$off","classList","type","STROKE_TYPES","STROKE_DEFAULT_COLOR","STROKE_DEFAULT_WIDTH","TEXT_HELPER_PADDING","TEXT_HELPER_ZINDEX","TEXT_HELPER_ID","TEXT_HELPER_FONT_SIZE","TEXT_FONT_FAMILY","AMBIGUITY_LEVEL","buildStrokePanel","createElement","style","cssText","innerHTML","buildFontPanel","buildAmbiguityPanel","ambiguity","getTextHelper","$textHelper","setAttribute","body","appendChild","insertTextHelper","event","state","threshold","padding","pos","getPos","x","pageX","y","pageY","maxW","Math","floor","width","maxH","height","styleMap","display","position","top","left","strokeColor","overflow","focus","removeTextHelper","defaults","container","$saveLink","createElementNS","canUseSaveLink","__downloadFile","fileName","Date","now","canvas","fileUrl","toDataURL","replace","setTimeout","href","download","dispatchEvent","MouseEvent","navigator","msToBlob","msSaveBlob","console","log","__bindEvents","self","$el","config","_handles","history","$btns","$fontPanel","$strokePanel","$mosaicPanel","$colorSelected","$colors","$strokeWidth","$fontSize","$mosaicAmbiguity","btnEmit","stopPropagation","drawType","__drawFont","getAttribute","value","$btn","isActive","visible","pop","putImageData","__toggleCanvasCursor","toggleColor","background","toggleStrokeWidth","strokeWidth","Number","method","toggleFontSize","_startPos","onMouseDown","lastImageData","getImageData","lineCap","lineJoin","shadowBlur","strokeStyle","lineWidth","__drawRect","__drawEllipse","__drawMoasic","__drawBrush","onMouseMove","onMouseUp","__pushHistory","isEntry","resize","_rect","getBoundingClientRect","offsetWidth","offsetHeight","toggleAmbiguity","start","clearRect","beginPath","strokeRect","restore","closePath","scaleX","scaleY","scale","arc","PI","moveTo","lineTo","content","textContent","parseFloat","lastSubStrIndex","fillStyle","char","measureText","fillText","substring","data","r","g","b","row","col","round","fillRect","cursor","CanvasTools","options","getContext","Error","create","render","C","S","parentNoed","removeChild"],"mappings":"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","file":"canvastools.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine(\"CanvasTools\", [], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"CanvasTools\"] = factory();\n\telse\n\t\troot[\"CanvasTools\"] = factory();\n})(this, function() {\nreturn \n\n\n// WEBPACK FOOTER //\n// webpack/universalModuleDefinition"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// identity function for calling harmony imports with the correct context\n \t__webpack_require__.i = function(value) { return value; };\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 4);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 07d30412e865d55647ad","// removed by extract-text-webpack-plugin\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/scss/canvastools.scss\n// module id = 0\n// module chunks = 0","//Element.closet Polyfill\r\n//https://developer.mozilla.org/en-US/docs/Web/API/Element/closest\r\nif (window.Element && !Element.prototype.closest) {\r\n Element.prototype.closest =\r\n function(s) {\r\n var matches = (this.document || this.ownerDocument).querySelectorAll(s),\r\n i,\r\n el = this;\r\n do {\r\n i = matches.length;\r\n while (--i >= 0 && matches.item(i) !== el) {};\r\n } while ((i < 0) && (el = el.parentElement));\r\n return el;\r\n };\r\n}\n\n\n// WEBPACK FOOTER //\n// ./src/js/closet.js","const ButtonsMap = {\r\n\trect: {\r\n\t\tpanel: 'stroke',\r\n\t\tname: '矩形工具'\r\n\t},\r\n\tellipse: {\r\n\t\tpanel: 'stroke',\r\n\t\tname: '椭圆工具'\r\n\t},\r\n\tbrush: {\r\n\t\tpanel: 'stroke',\r\n\t\tname: '画笔工具'\r\n\t},\r\n\tarrow: {\r\n\t\tpanel: 'stroke',\r\n\t\tname: '箭头工具'\r\n\t},\r\n\tmosaic: {\r\n\t\tpanel: 'mosaic',\r\n\t\tname: '马赛克工具'\r\n\t},\r\n\tfont: {\r\n\t\tpanel: 'font',\r\n\t\tname: '文字工具'\r\n\t},\r\n\trubber: {\r\n\t\tname: '橡皮擦'\r\n\t},\r\n\tundo: {\r\n\t\tname: '撤销操作'\r\n\t},\r\n\tsave: {\r\n\t\tname: '保存'\r\n\t}\r\n}\r\n\r\n\r\n//可用颜色\r\nconst ColorList = ['#000000', '#808080', '#800000', '#f7883a', '#308430', '#385ad3', '#800080', '#009999', '#ffffff', '#c0c0c0', '#fb3838', '#ffff00', '#99cc00', '#3894e4', '#f31bf3', '#16dcdc']\r\n\r\n//可选择字号,Chrome不支持小于12号的字体\r\nconst FontSize = [12, 14, 16, 18, 20, 22]\r\n\r\n//画笔大小\r\nconst StrokeWidth = [2, 4, 6]\r\n\r\n\r\n/**\r\n * 获取buttons模版\r\n * @param {Array} buttons [可用按钮]\r\n * @return {String} \r\n */\r\nconst getButtons = (buttons = []) => {\r\n\tlet html = []\r\n\tconst useButton = btn => {\r\n\t\treturn buttons && !!~buttons.indexOf(btn)\r\n\t}\r\n\tfor (let key in ButtonsMap) {\r\n\t\tif (useButton(key)) {\r\n\t\t\tlet btn = ButtonsMap[key]\r\n\t\t\thtml.push(`
    \r\n\t\t\t\r\n\t\t\t
    `)\r\n\t\t}\r\n\t}\r\n\treturn html.join('')\r\n}\r\n\r\n\r\n/**\r\n * 获取颜色面板\r\n * @param {String} color [当前选中色]\r\n * @return {String}\r\n */\r\nconst getColorPanel = (color = '#fb3838') => {\r\n\tlet html = ''\r\n\thtml += '
    '\r\n\thtml += ``\r\n\thtml += '
    '\r\n\r\n\tlet items1 = [],\r\n\t\titems2 = []\r\n\tfor (let i = 0; i < 16; i++) {\r\n\t\tlet item = `
  • `\r\n\t\tif (i < 8) {\r\n\t\t\titems1.push(item)\r\n\t\t} else {\r\n\t\t\titems2.push(item)\r\n\t\t}\r\n\t}\r\n\r\n\thtml += `
      ${items1.join('')}
    `\r\n\thtml += `
      ${items2.join('')}
    `\r\n\r\n\thtml += '
    '\r\n\thtml += '
    '\r\n\r\n\treturn html\r\n}\r\n\r\n\r\n/**\r\n * 获取画笔大小面板\r\n * @param {Number} stroke [当前画笔大小]\r\n * @return {String} \r\n */\r\nconst getStrokePanel = (stroke = 2) => {\r\n\tlet html = '
    '\r\n\tfor (let i = 0, len = StrokeWidth.length; i < len; i++) {\r\n\t\tlet size = StrokeWidth[i]\r\n\t\tlet classes = ['stroke', 'js-stroke-width']\r\n\t\tsize === stroke && classes.push('active')\r\n\t\thtml += ``\r\n\t}\r\n\thtml += '
    '\r\n\treturn html\r\n}\r\n\r\n\r\n/**\r\n * 获取字号选择器\r\n * @param {Number} fontSize [默认字号]\r\n * @return {String} \r\n */\r\nconst getFontPanel = (fontSize = 12) => {\r\n\tlet html = ''\r\n\treturn html\r\n}\r\n\r\n\r\n/**\r\n * 获取模糊度模版\r\n * @param {Number} ambiguite [默认模糊度 0 - 1]\r\n * @return {String} \r\n */\r\nconst getAmbiguity = (ambiguite = .5) => ``\r\n\r\nexport default {\r\n\tgetButtons,\r\n\tgetColorPanel,\r\n\tgetStrokePanel,\r\n\tgetFontPanel,\r\n\tgetAmbiguity,\r\n}\n\n\n// WEBPACK FOOTER //\n// ./src/js/template.js","const ArrayProto = Array.prototype\r\n\r\nconst SelectorRegs = {\r\n\tid: /^#([\\w-]+)$/,\r\n\tclassName: /^\\.([\\w-]+)$/,\r\n\ttagName: /^[\\w-]+$/\r\n}\r\n\r\n\r\nconst isObject = obj => obj !== null && typeof obj === 'object'\r\n\r\n\r\nconst isPlainObject = obj => Object.prototype.toString.call(obj) === '[object Object]'\r\n\r\n\r\n\r\n/**\r\n * 获取元素对象集合\r\n * @param {String} selector [选择器]\r\n * @param {HTMLElement} context [上下文对象]\r\n * @return {Array} [元素节点集合]\r\n */\r\nconst $ = (selector = '*', context = document) => {\r\n\tif (typeof selector === \"string\") {\r\n\t\tselector = selector.trim()\r\n\t\tlet dom = [];\r\n\t\tif (SelectorRegs.id.test(selector)) {\r\n\t\t\tdom = document.getElementById(RegExp.$1)\r\n\t\t\tdom = dom ? [dom] : []\r\n\t\t} else if (SelectorRegs.className.test(selector)) {\r\n\t\t\tdom = context.getElementsByClassName(RegExp.$1)\r\n\t\t} else if (SelectorRegs.tagName.test(selector)) {\r\n\t\t\tdom = context.getElementsByTagName(selector)\r\n\t\t} else {\r\n\t\t\tdom = context.querySelectorAll(selector)\r\n\t\t}\r\n\t\treturn ArrayProto.slice.call(dom)\r\n\t}\r\n\treturn []\r\n}\r\n\r\n\r\n/**\r\n * 对象遍历\r\n * @param {Object | Array} object [对象源]\r\n * @param {Function} callback [回调]\r\n * @return \r\n */\r\nconst each = (object, callback) => {\r\n\tif (typeof object === \"object\" && typeof callback === \"function\") {\r\n\t\tif (Array.isArray(object)) {\r\n\t\t\tfor (let i = 0, len = object.length; i < len; i++) {\r\n\t\t\t\tif (callback.call(object[i], i, object[i]) === false) {\r\n\t\t\t\t\tbreak\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if ('length' in object && typeof object.length === \"number\") { //这地方不太严谨,谨慎使用\r\n\t\t\tfor (let k in object) {\r\n\t\t\t\tif (callback.call(object[k], k, object[k]) === false) {\r\n\t\t\t\t\tbreak\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\n\r\n/**\r\n * 事件绑定,支持代理\r\n * @param {HTMLElement} element [DOM元素]\r\n * @param {String} eventType [事件类型]\r\n * @param {String} selector [选择器]\r\n * @param {Function} callback [回调]\r\n * @return \r\n */\r\nconst bind = (element, eventType, selector, callback) => {\r\n\tlet sel, handler;\r\n\tif (typeof selector === \"function\") {\r\n\t\thandler = selector\r\n\t} else if (typeof selector === \"string\" && typeof callback === \"function\") {\r\n\t\tsel = selector\r\n\t} else {\r\n\t\treturn\r\n\t}\r\n\tif (sel) { //事件代理\r\n\t\thandler = function(e) {\r\n\t\t\tconst nodes = $(sel, element)\r\n\t\t\tlet matched = false\r\n\t\t\tfor (let i = 0, len = nodes.length; i < len; i++) {\r\n\t\t\t\tconst node = nodes[i]\r\n\t\t\t\tif (node === e.target || node.contains(e.target)) {\r\n\t\t\t\t\tmatched = node\r\n\t\t\t\t\tbreak\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (matched) {\r\n\t\t\t\tcallback.apply(matched, ArrayProto.slice.call(arguments))\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\telement.addEventListener(eventType, handler, false)\r\n}\r\n\r\n\r\n/**\r\n * 事件解绑\r\n * @param {HTMLElement} element [DOM元素]\r\n * @param {String} eventType [事件类型]\r\n * @param {Function} callback [回调]\r\n * @return \r\n */\r\nconst unbind = (element, eventType, callback) => element.removeEventListener(eventType, callback, false)\r\n\r\n\r\n/**\r\n * 获取指定元素样式\r\n * @param {HTMLElement} element\r\n * @return {Object} \r\n */\r\nconst getComputedStyles = element => element.ownerDocument.defaultView.getComputedStyle(element, null)\r\n\r\n\r\n/**\r\n * 对象深度拷贝\r\n * @param {Object} out\r\n * @return {Object}\r\n */\r\nconst extend = function(out = {}) {\r\n\tfor (let i = 1, len = arguments.length; i < len; i++) {\r\n\t\tlet obj = arguments[i]\r\n\t\tif (!obj || !Object.keys(obj).length)\r\n\t\t\tcontinue\r\n\t\tfor (let key in obj) {\r\n\t\t\tif (obj.hasOwnProperty(key)) {\r\n\t\t\t\tif (isPlainObject(obj[key]))\r\n\t\t\t\t\tout[key] = extend(out[key], obj[key])\r\n\t\t\t\telse\r\n\t\t\t\t\tout[key] = obj[key]\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\treturn out\r\n}\r\n\r\n\r\nconst $on = (elements, eventType, selector, callback) => {\r\n\tif (!Array.isArray(elements)) {\r\n\t\telements = [elements]\r\n\t}\r\n\teach(elements, (index, element) => bind(element, eventType, selector, callback))\r\n}\r\n\r\nconst $off = (elements, eventType, callback) => {\r\n\tif (!Array.isArray(elements)) {\r\n\t\telements = [elements]\r\n\t}\r\n\teach(elements, (index, element) => unbind(element, eventType, callback))\r\n}\r\n\r\nconst classList = (elements, type = 'add', classes = '') => {\r\n\tif (!Array.isArray(elements)) {\r\n\t\telements = [elements]\r\n\t}\r\n\teach(elements, (index, element) => element.classList[type](classes))\r\n}\r\n\r\n\r\nexport default {\r\n\t$,\r\n\teach,\r\n\tbind,\r\n\textend,\r\n\tunbind,\r\n\tgetComputedStyles,\r\n\tclassList,\r\n\t$on,\r\n\t$off,\r\n}\n\n\n// WEBPACK FOOTER //\n// ./src/js/utils.js","import '../scss/canvastools.scss'\r\nimport './closet'\r\nimport utils from './utils'\r\nimport Template from './template'\r\n\r\n//stroke类型操作\r\nconst STROKE_TYPES = ['rect', 'ellipse', 'brush', 'arrow', 'mosaic', 'font', 'rubber']\r\n\r\n//默认颜色\r\nconst STROKE_DEFAULT_COLOR = '#fb3838'\r\n\r\n//默认画笔大小\r\nconst STROKE_DEFAULT_WIDTH = 2\r\n\r\n//辅助输入框padding值\r\nconst TEXT_HELPER_PADDING = 2\r\n\r\n//辅助输入框层级\r\nconst TEXT_HELPER_ZINDEX = 1990\r\n\r\n//辅助输入框ID\r\nconst TEXT_HELPER_ID = 'canvas-tools_text_helper'\r\n\r\n//输入框默认字体大小\r\n//以设置的字体大小为准,改值仅做辅助值\r\nconst TEXT_HELPER_FONT_SIZE = 12\r\n\r\n//字体\r\nconst TEXT_FONT_FAMILY = '\"Helvetica Neue\",Helvetica,Arial,\"Hiragino Sans GB\",\"Hiragino Sans GB W3\",\"WenQuanYi Micro Hei\",sans-serif'\r\n\r\n//马赛克模糊度\r\nconst AMBIGUITY_LEVEL = .7\r\n\r\n/**\r\n * 创建画笔+颜色容器\r\n * @param {Number} stroke [默认画笔]\r\n * @param {String} color [默认颜色]\r\n * @return {HTMLElement} \r\n */\r\nconst buildStrokePanel = (stroke = STROKE_DEFAULT_COLOR, color = STROKE_DEFAULT_COLOR) => {\r\n\tconst el = document.createElement('div')\r\n\tel.className = 'canvas-tools__panel js-panel__stroke'\r\n\tel.style.cssText += 'white-space: nowrap!important;'\r\n\tel.innerHTML = Template.getStrokePanel(stroke) + Template.getColorPanel(color)\r\n\treturn el\r\n}\r\n\r\n/**\r\n * 创建字号+颜色容器\r\n * @param {Number} fontSize [默认字号]\r\n * @param {String} color [默认颜色]\r\n * @return {HTMLElement} \r\n */\r\nconst buildFontPanel = (fontSize = TEXT_HELPER_FONT_SIZE, color = STROKE_DEFAULT_COLOR) => {\r\n\tconst el = document.createElement('div')\r\n\tel.className = 'canvas-tools__panel js-panel__font'\r\n\tel.style.cssText += 'white-space: nowrap!important;'\r\n\tel.innerHTML = Template.getFontPanel(fontSize) + Template.getColorPanel(color)\r\n\treturn el\r\n}\r\n\r\n/**\r\n * 创建画笔 + 模糊度容器\r\n * @param {Number} stroke [默认画笔]\r\n * @param {Number} ambiguity [默认模糊度]\r\n * @return {HTMLElement} \r\n */\r\nconst buildAmbiguityPanel = (stroke = STROKE_DEFAULT_COLOR, ambiguity = AMBIGUITY_LEVEL) => {\r\n\tconst el = document.createElement('div')\r\n\tel.className = 'canvas-tools__panel js-panel__mosaic'\r\n\tel.style.cssText += 'white-space: nowrap!important;'\r\n\tel.innerHTML = Template.getStrokePanel(stroke) + Template.getAmbiguity(ambiguity)\r\n\treturn el\r\n}\r\n\r\n/**\r\n * 创建辅助文本输入框\r\n * @return {HTMLElement}\r\n */\r\nconst getTextHelper = () => {\r\n\tlet $textHelper = document.getElementById(TEXT_HELPER_ID)\r\n\tif (!$textHelper) {\r\n\t\t$textHelper = document.createElement('div')\r\n\t\t$textHelper.setAttribute('contenteditable', 'plaintext-only')\r\n\t\t$textHelper.setAttribute('spellcheck', 'false')\r\n\t\t$textHelper.setAttribute('id', TEXT_HELPER_ID)\r\n\t\t$textHelper.className = TEXT_HELPER_ID\r\n\t\tdocument.body.appendChild($textHelper)\r\n\t}\r\n\t$textHelper.innerHTML = ''\r\n\t$textHelper.style.cssText = 'display: block';\r\n\treturn $textHelper\r\n}\r\n\r\n/**\r\n * 初始化辅助文本输入框样式\r\n * @param {MouseEvent} event [鼠标事件]\r\n * @param {Object} state [instance state]\r\n * @param {Object} rect [instance rect]\r\n * @return {HTMLElement} \r\n */\r\nconst insertTextHelper = (event, state, rect) => {\r\n\tconst $textHelper = getTextHelper(),\r\n\t\tthreshold = state.fontSize || TEXT_HELPER_FONT_SIZE,\r\n\t\tpadding = 2 * TEXT_HELPER_PADDING + 2,\r\n\t\tpos = getPos(event, rect)\r\n\r\n\tlet x = event.pageX,\r\n\t\ty = event.pageY,\r\n\t\tmaxW = Math.floor(rect.width - pos.x) - padding,\r\n\t\tmaxH = Math.floor(rect.height - pos.y) - padding\r\n\r\n\tif (maxW <= threshold) {\r\n\t\tx -= (threshold + padding - maxW)\r\n\t\tmaxW = threshold\r\n\t}\r\n\r\n\tif (maxH <= threshold) {\r\n\t\ty -= (threshold + padding - maxH)\r\n\t\tmaxH = threshold\r\n\t}\r\n\r\n\tif (maxW >= rect.width) {\r\n\t\tmaxW = rect.width - pos.x\r\n\t}\r\n\r\n\tif (maxH >= rect.height) {\r\n\t\tmaxH = rect.height - pos.y\r\n\t}\r\n\r\n\tconst styleMap = {\r\n\t\t'font-size': threshold + 'px',\r\n\t\t'line-height': threshold + 'px',\r\n\t\t'min-width': threshold + 'px',\r\n\t\t'min-height': threshold + 'px',\r\n\t\t'max-width': maxW + 'px',\r\n\t\t'max-height': maxH + 'px',\r\n\t\t'z-index': TEXT_HELPER_ZINDEX,\r\n\t\t'font-family': TEXT_FONT_FAMILY,\r\n\t\tdisplay: 'block',\r\n\t\tposition: 'absolute',\r\n\t\ttop: y + 'px',\r\n\t\tleft: x + 'px',\r\n\t\tcolor: state.strokeColor,\r\n\t\tpadding: TEXT_HELPER_PADDING + 'px',\r\n\t\toverflow: 'hidden',\r\n\t}\r\n\r\n\tlet style = ''\r\n\r\n\tfor (let key in styleMap) {\r\n\t\tstyle += `${key}:${styleMap[key]};`\r\n\t}\r\n\r\n\t$textHelper.style.cssText = style\r\n\t$textHelper.focus()\r\n\treturn $textHelper\r\n}\r\n\r\n/**\r\n * 移除辅助文本输入框\r\n * @return \r\n */\r\nconst removeTextHelper = () => {\r\n\tlet $textHelper = document.getElementById(TEXT_HELPER_ID)\r\n\tif (!$textHelper) {\r\n\t\treturn\r\n\t}\r\n\t$textHelper.innerHTML = ''\r\n\t$textHelper.style.cssText = 'display: none';\r\n}\r\n\r\n\r\n/**\r\n * 获取鼠标在Canvas上的位置\r\n * @param {Element Event} event [鼠标事件]\r\n * @param {Object} rect [Canvas rect]\r\n * @return {Object} \r\n */\r\nconst getPos = (event, rect) => {\r\n\tlet x = event.pageX - rect.left\r\n\tlet y = event.pageY - rect.top\r\n\tif (x <= 0) x = 0\r\n\tif (x >= rect.width) x = rect.width\r\n\tif (y <= 0) y = 0\r\n\tif (y >= rect.height) y = rect.height\r\n\r\n\treturn {\r\n\t\tx,\r\n\t\ty\r\n\t}\r\n}\r\n\r\n\r\n/**\r\n * 默认配置\r\n * @type {Object}\r\n */\r\nconst defaults = {\r\n\t//工具条父级对象容器\r\n\tcontainer: document.body,\r\n\t//显示按钮\r\n\tbuttons: ['rect', 'ellipse', 'brush', 'font', 'mosaic', 'undo', 'save']\r\n}\r\n\r\n//创建一个下载链接\r\nconst $saveLink = document.createElementNS('http://www.w3.org/1999/xhtml', 'a')\r\n\r\n//是否支持原生下载\r\nconst canUseSaveLink = 'download' in $saveLink\r\n\r\n//下载文件\r\nconst __downloadFile = function() {\r\n\tconst fileName = `canvas_${Date.now()}.png`\r\n\tconst canvas = this.canvas\r\n\r\n\tif (canUseSaveLink) {\r\n\t\tlet fileUrl = canvas.toDataURL('png')\r\n\t\tfileUrl = fileUrl.replace('image/png', 'image/octet-stream')\r\n\t\tsetTimeout(() => {\r\n\t\t\t$saveLink.href = fileUrl\r\n\t\t\t$saveLink.download = fileName\r\n\r\n\t\t\t//触发click事件\r\n\t\t\t$saveLink.dispatchEvent(new MouseEvent('click'))\r\n\t\t})\r\n\t}\r\n\r\n\t//for ie 10+ \r\n\telse if (typeof navigator !== \"undefined\" && typeof canvas.msToBlob === 'function' && navigator.msSaveBlob) {\r\n\t\tnavigator.msSaveBlob(canvas.msToBlob(), fileName)\r\n\t}\r\n\r\n\t// other \r\n\telse {\r\n\t\tconsole.log('您的浏览器不支持该操作')\r\n\t}\r\n}\r\n\r\n\r\n//相关事件绑定\r\nfunction __bindEvents() {\r\n\tconst self = this\r\n\tconst {\r\n\t\tcanvas,\r\n\t\tcontext,\r\n\t\t$el,\r\n\t\tstate,\r\n\t\tconfig,\r\n\t\trect,\r\n\t\t_handles,\r\n\t\thistory\r\n\t} = this\r\n\r\n\tconst $btns = utils.$('.js-btn', $el),\r\n\t\t$fontPanel = utils.$('.js-panel__font', $el)[0],\r\n\t\t$strokePanel = utils.$('.js-panel__stroke', $el)[0],\r\n\t\t$mosaicPanel = utils.$('.js-panel__mosaic', $el)[0],\r\n\t\t$colorSelected = utils.$('.js-color-selected', $el),\r\n\t\t$colors = utils.$('.js-color', $el),\r\n\t\t$strokeWidth = utils.$('.js-stroke-width', $el),\r\n\t\t$fontSize = utils.$('.js-font-size', $el),\r\n\t\t$mosaicAmbiguity = utils.$('.js-mosaic-ambiguity', $el)\r\n\r\n\t//按钮事件\r\n\t_handles.btnEmit = function(event) {\r\n\t\tevent.stopPropagation()\r\n\t\tif (state.drawType === 'font') {\r\n\t\t\t__drawFont.call(self, event)\r\n\t\t}\r\n\t\tconst panel = this.getAttribute('data-panel')\r\n\t\tconst value = this.getAttribute('data-value')\r\n\t\tutils.each($btns, (index, $btn) => {\r\n\t\t\tif ($btn !== this) {\r\n\t\t\t\tutils.classList($btn, 'remove', 'active')\r\n\t\t\t}\r\n\t\t})\r\n\t\tif (!!~STROKE_TYPES.indexOf(value)) {\r\n\t\t\tstate.drawType = value\r\n\t\t}\r\n\t\tif (panel) {\r\n\t\t\tutils.classList(this, 'toggle', 'active')\r\n\t\t\tconst isActive = /active/.test(this.className)\r\n\t\t\tconst visible = isActive ? 'block' : 'none'\r\n\t\t\tif (panel === 'stroke') {\r\n\t\t\t\t$fontPanel.style.display = 'none'\r\n\t\t\t\t$mosaicPanel.style.display = 'none'\r\n\t\t\t\t$strokePanel.style.display = visible\r\n\t\t\t} else if (panel === 'font') {\r\n\t\t\t\t$fontPanel.style.display = visible\r\n\t\t\t\t$strokePanel.style.display = 'none'\r\n\t\t\t\t$mosaicPanel.style.display = 'none'\r\n\t\t\t} else if (panel === 'mosaic') {\r\n\t\t\t\t$mosaicPanel.style.display = visible\r\n\t\t\t\t$fontPanel.style.display = 'none'\r\n\t\t\t\t$strokePanel.style.display = 'none'\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\t//$fontPanel.style.display = 'none'\r\n\t\t\t//$strokePanel.style.display = 'none'\r\n\t\t}\r\n\r\n\t\tif (value === 'save') {\r\n\t\t\t__downloadFile.call(self)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t//history[0]是画布的初始状态\r\n\t\t//因此只有多于1个历史记录时才可以恢复上一步\r\n\t\tif (value === 'undo' && history.length > 1) {\r\n\t\t\thistory.pop()\r\n\t\t\tcontext.putImageData(history[history.length - 1], 0, 0, 0, 0, rect.width, rect.height)\r\n\t\t}\r\n\r\n\t\t__toggleCanvasCursor.call(self)\r\n\t}\r\n\r\n\t_handles.toggleColor = function(event) {\r\n\t\tconst color = this.getAttribute('data-value')\r\n\t\tstate.strokeColor = color\r\n\t\tutils.each($colorSelected, (index, item) => item.style.background = color)\r\n\t}\r\n\r\n\t_handles.toggleStrokeWidth = function(event) {\r\n\t\tstate.strokeWidth = Number(this.getAttribute('data-value'))\r\n\t\tutils.each($strokeWidth, (index, item) => {\r\n\t\t\tconst value = Number(item.getAttribute('data-value'))\r\n\t\t\tconst method = value === state.strokeWidth ? 'add' : 'remove'\r\n\t\t\tutils.classList(item, method, 'active')\r\n\t\t})\r\n\t}\r\n\r\n\t_handles.toggleFontSize = function(event) {\r\n\t\tstate.fontSize = Number(this.value)\r\n\t}\r\n\r\n\t//鼠标在画布上的初始位置\r\n\tlet _startPos\r\n\r\n\t_handles.onMouseDown = function(event) {\r\n\t\tif (!!~STROKE_TYPES.indexOf(state.drawType) === false || state.drawType === 'font') {\r\n\t\t\treturn\r\n\t\t}\r\n\t\t_startPos = getPos(event, rect)\r\n\r\n\t\t//保存当前快照\r\n\t\tstate.lastImageData = context.getImageData(0, 0, rect.width, rect.height)\r\n\r\n\t\t//初始化context状态\r\n\t\tcontext.lineCap = 'round'\r\n\t\tcontext.lineJoin = 'round'\r\n\t\tcontext.shadowBlur = 0\r\n\t\tcontext.strokeStyle = state.strokeColor\r\n\t\tcontext.lineWidth = state.strokeWidth\r\n\t\tswitch (state.drawType) {\r\n\t\t\tcase 'rect':\r\n\t\t\t\t__drawRect.call(self, event, _startPos)\r\n\t\t\t\tbreak\r\n\t\t\tcase 'ellipse':\r\n\t\t\t\t__drawEllipse.call(self, event, _startPos)\r\n\t\t\t\tbreak\r\n\t\t\tcase 'mosaic':\r\n\t\t\t\t__drawMoasic.call(self, event)\r\n\t\t\t\tbreak\r\n\t\t\tcase 'brush':\r\n\t\t\tdefault:\r\n\t\t\t\t__drawBrush.call(self, event, _startPos)\r\n\t\t\t\tbreak\r\n\t\t}\r\n\r\n\t\tutils.$on(document, 'mousemove', _handles.onMouseMove)\r\n\t\tutils.$on(document, 'mouseup', _handles.onMouseUp)\r\n\t}\r\n\r\n\t_handles.onMouseMove = function(event) {\r\n\t\tif (!!~STROKE_TYPES.indexOf(state.drawType) === false || state.drawType === 'font') {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tswitch (state.drawType) {\r\n\t\t\tcase 'rect':\r\n\t\t\t\t__drawRect.call(self, event, _startPos)\r\n\t\t\t\tbreak\r\n\t\t\tcase 'ellipse':\r\n\t\t\t\t__drawEllipse.call(self, event, _startPos)\r\n\t\t\t\tbreak\r\n\t\t\tcase 'mosaic':\r\n\t\t\t\t__drawMoasic.call(self, event)\r\n\t\t\t\tbreak\r\n\t\t\tcase 'brush':\r\n\t\t\tdefault:\r\n\t\t\t\t__drawBrush.call(self, event, null)\r\n\t\t\t\tbreak\r\n\t\t}\r\n\t}\r\n\r\n\t_handles.onMouseUp = function(event) {\r\n\t\tutils.$off(document, 'mousemove', _handles.onMouseMove)\r\n\t\tutils.$off(document, 'mouseup', _handles.onMouseUp)\r\n\t\tif (!!~STROKE_TYPES.indexOf(state.drawType) && state.drawType !== 'font') {\r\n\t\t\t__pushHistory.call(self)\r\n\t\t}\r\n\t}\r\n\r\n\t_handles.insertTextHelper = function(event) {\r\n\t\tevent.stopPropagation()\r\n\t\tif (state.drawType !== 'font') {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tif (state.isEntry) {\r\n\t\t\t__drawFont.call(self, event)\r\n\t\t\treturn\r\n\t\t}\r\n\t\tinsertTextHelper(event, state, rect)\r\n\t\tstate.isEntry = true\r\n\t}\r\n\r\n\t_handles.removeTextHelper = function(event) {\r\n\t\tif (state.drawType !== 'font') {\r\n\t\t\tremoveTextHelper()\r\n\t\t} else if (!event.target.closest('#canvas-tools-input')) {\r\n\t\t\t__drawFont.call(self, event)\r\n\t\t}\r\n\t}\r\n\r\n\t_handles.resize = function(event) {\r\n\t\tconst _rect = canvas.getBoundingClientRect()\r\n\t\trect.width = canvas.width\r\n\t\trect.height = canvas.height\r\n\t\trect.offsetWidth = canvas.offsetWidth\r\n\t\trect.offsetHeight = canvas.offsetHeight\r\n\t\trect.top = _rect.top\r\n\t\trect.left = _rect.left\r\n\t\tstate.drawType === 'font' && state.isEntry && __drawFont.call(self, event)\r\n\t}\r\n\r\n\t_handles.toggleAmbiguity = function(event) {\r\n\t\tstate.ambiguity = this.value\r\n\t\tconsole.log(state)\r\n\t}\r\n\r\n\t//按钮事件\r\n\tutils.$on($btns, 'click', _handles.btnEmit)\r\n\r\n\t//切换颜色\r\n\tutils.$on($colors, 'click', _handles.toggleColor)\r\n\r\n\t//切换画笔大小\r\n\tutils.$on($strokeWidth, 'click', _handles.toggleStrokeWidth)\r\n\r\n\t//切换字体大小\r\n\tutils.$on($fontSize, 'change', _handles.toggleFontSize)\r\n\r\n\tutils.$on($mosaicAmbiguity, 'change', _handles.toggleAmbiguity)\r\n\r\n\t//矩形,椭圆,画笔等绘制\r\n\tutils.$on(canvas, 'mousedown', _handles.onMouseDown)\r\n\r\n\t//插入文本辅助框\r\n\tutils.$on(canvas, 'click', _handles.insertTextHelper)\r\n\r\n\t//移除文本辅助框\r\n\tutils.$on(document, 'click', _handles.removeTextHelper)\r\n\r\n\t//window resize\r\n\twindow.addEventListener('resize', _handles.resize)\r\n}\r\n\r\n/**\r\n * 绘制矩形\r\n * @param {MouseEvent} event [鼠标事件]\r\n * @param {Object} start [起始位置]\r\n * @return \r\n */\r\nfunction __drawRect(event, start) {\r\n\tconst {\r\n\t\tcontext,\r\n\t\trect,\r\n\t\tstate\r\n\t} = this\r\n\tconst pos = getPos(event, rect)\r\n\tlet width = pos.x - start.x\r\n\tlet height = pos.y - start.y\r\n\tcontext.clearRect(0, 0, rect.width, rect.height)\r\n\tcontext.putImageData(state.lastImageData, 0, 0, 0, 0, rect.width, rect.height)\r\n\tcontext.save()\r\n\tcontext.beginPath()\r\n\tcontext.strokeRect(start.x, start.y, width, height)\r\n\tcontext.restore()\r\n\tcontext.closePath()\r\n}\r\n\r\n\r\n/**\r\n * 绘制椭圆\r\n * @param {MouseEvent} event [鼠标事件]\r\n * @param {Object} start [起始位置]\r\n * @return \r\n */\r\nfunction __drawEllipse(event, start) {\r\n\tconst {\r\n\t\tcontext,\r\n\t\trect,\r\n\t\tstate\r\n\t} = this\r\n\tconst pos = getPos(event, rect)\r\n\tlet scaleX = 1 * ((pos.x - start.x) / 2)\r\n\tlet scaleY = 1 * ((pos.y - start.y) / 2)\r\n\tlet x = (start.x / scaleX) + 1\r\n\tlet y = (start.y / scaleY) + 1\r\n\tcontext.clearRect(0, 0, rect.width, rect.height)\r\n\tcontext.putImageData(state.lastImageData, 0, 0, 0, 0, rect.width, rect.height)\r\n\tcontext.save()\r\n\tcontext.beginPath()\r\n\tcontext.scale(scaleX, scaleY)\r\n\tcontext.arc(x, y, 1, 0, 2 * Math.PI)\r\n\tcontext.restore()\r\n\tcontext.closePath()\r\n\tcontext.stroke()\r\n}\r\n\r\n\r\n/**\r\n * 画笔工具自由绘制\r\n * @param {MouseEvent} event [鼠标事件]\r\n * @param {Object | null} start [起始位置]\r\n * @return \r\n */\r\nfunction __drawBrush(event, start = null) {\r\n\tconst {\r\n\t\tcontext,\r\n\t\trect\r\n\t} = this\r\n\tif (start) {\r\n\t\tcontext.beginPath()\r\n\t\tcontext.moveTo(start.x, start.y)\r\n\t} else {\r\n\t\tconst pos = getPos(event, rect)\r\n\t\tcontext.lineTo(pos.x, pos.y)\r\n\t\tcontext.stroke()\r\n\t}\r\n}\r\n\r\n\r\n/**\r\n * 绘制文字\r\n * @param {MouseEvent} event [鼠标事件]\r\n * @return {[type]} \r\n */\r\nfunction __drawFont(event) {\r\n\tconst $textHelper = document.getElementById(TEXT_HELPER_ID)\r\n\tif (!$textHelper) {\r\n\t\tthis.state.isEntry = false\r\n\t\treturn\r\n\t}\r\n\tconst content = $textHelper.textContent.trim()\r\n\tconst length = content.length\r\n\tif (!content || !length) {\r\n\t\tthis.state.isEntry = false\r\n\t\tremoveTextHelper()\r\n\t\treturn\r\n\t}\r\n\tconst style = utils.getComputedStyles($textHelper),\r\n\t\tthreshold = this.state.fontSize || TEXT_HELPER_FONT_SIZE,\r\n\t\tpadding = 2 * TEXT_HELPER_PADDING,\r\n\t\tcontext = this.context\r\n\r\n\tlet x = parseFloat(style.left) - this.rect.left + padding - 2,\r\n\t\ty = parseFloat(style.top) - this.rect.top + threshold,\r\n\t\tlineWidth = 0,\r\n\t\tlastSubStrIndex = 0\r\n\r\n\tcontext.beginPath()\r\n\tcontext.save()\r\n\tcontext.fillStyle = this.state.strokeColor\r\n\tcontext.font = `${this.state.fontSize}px ${TEXT_FONT_FAMILY}`\r\n\r\n\tfor (let i = 0; i < length; i++) {\r\n\t\tlet char = content[i]\r\n\r\n\t\t//让文字自动换行\r\n\t\tlineWidth += context.measureText(char).width\r\n\t\tif (lineWidth > this.rect.width - x) {\r\n\t\t\tcontext.fillText(content.substring(lastSubStrIndex, i), x, y)\r\n\t\t\ty += threshold\r\n\t\t\tlineWidth = 0\r\n\t\t\tlastSubStrIndex = i\r\n\t\t}\r\n\t\tif (i == length - 1) {\r\n\t\t\tcontext.fillText(content.substring(lastSubStrIndex, i + 1), x, y);\r\n\t\t}\r\n\t}\r\n\tcontext.restore()\r\n\tcontext.closePath()\r\n\tthis.state.isEntry = false\r\n\tremoveTextHelper()\r\n\t__pushHistory.call(this)\r\n}\r\n\r\n\r\n/**\r\n * 绘制马赛克\r\n * @param {MouseEvent} event [鼠标事件]\r\n * @return {[type]} \r\n */\r\nfunction __drawMoasic(event) {\r\n\tconst {\r\n\t\tcontext,\r\n\t\tstate,\r\n\t\trect\r\n\t} = this\r\n\tconst pos = getPos(event, rect)\r\n\tconst size = state.strokeWidth * 3\r\n\r\n\t//获取当前位置1PX的颜色值\r\n\tconst data = context.getImageData(pos.x, pos.y, size, size).data\r\n\r\n\tlet r = 0, g = 0, b = 0\r\n\t\r\n\tfor (let row = 0; row < size; row ++) {\r\n\t\tfor (let col = 0; col < size; col++) {\r\n\t\t\t r += data[((size * row) + col) * 4]\r\n g += data[((size * row) + col) * 4 + 1]\r\n b += data[((size * row) + col) * 4 + 2]\r\n\t\t}\r\n\t}\r\n\r\n\tr = Math.round(r / (size * size))\r\n\tg = Math.round(g / (size * size))\r\n\tb = Math.round(b / (size * size))\r\n\tconst color = `rgba(${r}, ${g}, ${b}, ${state.ambiguity})`\r\n\tcontext.beginPath()\r\n\tcontext.save()\r\n\tcontext.fillStyle = color\r\n\tcontext.fillRect(pos.x, pos.y, size, size)\r\n\tcontext.restore()\r\n}\r\n\r\n\r\n/**\r\n * 切换鼠标指针\r\n * @return \r\n */\r\nfunction __toggleCanvasCursor() {\r\n\tconst canvas = this.canvas\r\n\tlet cursor\r\n\tswitch (this.state.drawType) {\r\n\t\tcase 'brush':\r\n\t\t\tcursor = 'brush'\r\n\t\t\tbreak\r\n\t\tcase 'font':\r\n\t\t\tcursor = 'font'\r\n\t\t\tbreak\r\n\t\tcase 'mosaic':\r\n\t\t\tcursor = 'mosaic'\r\n\t\t\tbreak\r\n\t\tcase 'rect':\r\n\t\tcase 'ellipse':\r\n\t\t\tcursor = 'crosshair'\r\n\t\t\tbreak\r\n\t\tdefault:\r\n\t\t\tcursor = 'default'\r\n\t}\r\n\tif (cursor) {\r\n\t\tcanvas.className = canvas.className.replace(/canvas-cursor__(\\w+)/, '').trim() + ` canvas-cursor__${cursor}`\r\n\t}\r\n}\r\n\r\n\r\n/**\r\n * 保存到历史记录\r\n * @return \r\n */\r\nfunction __pushHistory() {\r\n\tthis.history.push(this.context.getImageData(0, 0, this.rect.width, this.rect.height))\r\n}\r\n\r\n/**\r\n * CanvasTools\r\n * Class\r\n */\r\nclass CanvasTools {\r\n\r\n\t/**\r\n\t * constructor\r\n\t * @param {CanvasElement} canvas [canvas element object]\r\n\t * @param {Object} options [config]\r\n\t * @return {Object} [instance]\r\n\t */\r\n\tconstructor(canvas, options) {\r\n\t\tif (!canvas || typeof canvas.getContext !== 'function') {\r\n\t\t\tthrow new Error('invalid canvas object')\r\n\t\t}\r\n\t\tthis.canvas = canvas\r\n\t\tthis.context = canvas.getContext('2d')\r\n\t\tthis.config = utils.extend({}, defaults, options || {})\r\n\r\n\t\tthis.history = []\r\n\t\tthis.state = Object.create(null)\r\n\t\tthis.rect = Object.create(null)\r\n\t\tthis._handles = Object.create(null)\r\n\r\n\t\tthis.state.strokeWidth = STROKE_DEFAULT_WIDTH\r\n\t\tthis.state.fontSize = TEXT_HELPER_FONT_SIZE\r\n\t\tthis.state.strokeColor = STROKE_DEFAULT_COLOR\r\n\t\tthis.state.ambiguity = AMBIGUITY_LEVEL\r\n\t\tthis.state.drawType = 'brush'\r\n\t\tthis.state.isEntry = false\r\n\r\n\t\tthis.rect.width = canvas.width\r\n\t\tthis.rect.height = canvas.height\r\n\r\n\t\tthis.rect.offsetWidth = canvas.offsetWidth\r\n\t\tthis.rect.offsetHeight = canvas.offsetHeight\r\n\r\n\t\tconst rect = canvas.getBoundingClientRect()\r\n\t\tthis.rect.top = rect.top\r\n\t\tthis.rect.left = rect.left\r\n\r\n\t\t//保存现场\r\n\t\tthis.state.lastImageData = this.context.getImageData(0, 0, this.rect.width, this.rect.height)\r\n\r\n\t\t//将画布的初始状态保存到历史记录\r\n\t\t__pushHistory.call(this)\r\n\r\n\t\t__toggleCanvasCursor.call(this)\r\n\r\n\t\tthis.render()\r\n\t}\r\n\r\n\t/**\r\n\t * 初始化工具条到DOM\r\n\t * @return\r\n\t */\r\n\trender() {\r\n\t\tconst C = this.config\r\n\t\tconst S = this.state\r\n\t\tlet el = document.createElement('div')\r\n\t\tel.className = 'canvas-tools'\r\n\t\tel.innerHTML = Template.getButtons(C.buttons)\r\n\t\tC.container.appendChild(el)\r\n\r\n\t\tthis.$el = el\r\n\t\tthis.$el.appendChild(buildStrokePanel(S.strokeWidth, S.strokeColor))\r\n\t\tthis.$el.appendChild(buildFontPanel(S.fontSize, S.strokeColor))\r\n\t\tthis.$el.appendChild(buildAmbiguityPanel(S.strokeWidth, S.ambiguity))\r\n\t\t__bindEvents.call(this)\r\n\t}\r\n\r\n\trefresh() {\r\n\r\n\t}\r\n\r\n\t/**\r\n\t * destory\r\n\t * @return \r\n\t */\r\n\tdestory() {\r\n\t\tconst {\r\n\t\t\tcanvas,\r\n\t\t\t$el,\r\n\t\t\t_handles\r\n\t\t} = this\r\n\r\n\t\tconst $btns = utils.$('.js-btn', $el),\r\n\t\t\t$colors = utils.$('.js-color', $el),\r\n\t\t\t$strokeWidth = utils.$('.js-stroke-width', $el),\r\n\t\t\t$fontSize = utils.$('.js-font-size', $el),\r\n\t\t\t$mosaicAmbiguity = utils.$('.js-mosaic-ambiguity', $el),\r\n\t\t\t$textHelper = document.getElementById(TEXT_HELPER_ID)\r\n\r\n\t\tutils.$off($btns, 'click', _handles.btnEmit)\r\n\t\tutils.$off($colors, 'click', _handles.toggleColor)\r\n\t\tutils.$off($strokeWidth, 'click', _handles.toggleStrokeWidth)\r\n\t\tutils.$off($fontSize, 'change', _handles.toggleFontSize)\r\n\t\tutils.$off($mosaicAmbiguity, 'change', _handles.toggleAmbiguity)\r\n\t\tutils.$off(canvas, 'mousedown', _handles.onMouseDown)\r\n\t\tutils.$off(canvas, 'click', _handles.insertTextHelper)\r\n\t\tutils.$off(document, 'click', _handles.removeTextHelper)\r\n\t\twindow.removeEventListener('resize', _handles.resize)\r\n\t\t$textHelper && $textHelper.parentNoed.removeChild($textHelper)\r\n\t\tthis.canvas = null\r\n\t\tthis.context = null\r\n\t\tthis.history.length = 0\r\n\t\tthis.config.container.removeChild(this.$el)\r\n\t}\r\n\r\n}\r\n\r\nexport default CanvasTools\n\n\n// WEBPACK FOOTER //\n// ./src/js/main.js"],"sourceRoot":""} --------------------------------------------------------------------------------