├── .eslintrc.js ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.js ├── README.md ├── build └── gh.js ├── demo ├── App.vue ├── cover.jpg ├── index.ejs └── index.js ├── env ├── gh.js └── pro.js ├── package.json ├── postcss.config.js ├── src ├── color │ ├── Alpha.vue │ ├── Box.vue │ ├── Colors.vue │ ├── Hue.vue │ ├── Index.vue │ ├── Preview.vue │ ├── Saturation.vue │ ├── Sucker.vue │ └── mixin.js ├── img │ ├── paypal.png │ ├── preview-dark.jpg │ ├── preview-light.jpg │ ├── sucker.png │ └── wechat.jpg └── index.js ├── webpack.config.js └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | node: true, 5 | commonjs: true, 6 | es6: true 7 | }, 8 | extends: ['eslint:recommended', 'plugin:vue/recommended'], 9 | parserOptions: { 10 | ecmaVersion: 2018, 11 | sourceType: 'module' 12 | }, 13 | plugins: [ 14 | // 'html' 15 | // 'vue' 16 | ], 17 | globals: { 18 | Vue: true 19 | }, 20 | rules: { 21 | indent: [ 22 | 'error', 23 | 4, 24 | { 25 | SwitchCase: 1 26 | } 27 | ], 28 | 'linebreak-style': ['error', 'unix'], 29 | quotes: ['error', 'single'], 30 | semi: ['error', 'never'], 31 | 'vue/html-indent': ['error', 4], 32 | 'vue/object-curly-spacing': ['error', 'always'], 33 | 'object-curly-spacing': ['error', 'always'] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | node_modules/ 4 | bower_components/ 5 | dist/ 6 | zip/ 7 | *.log* 8 | env/local* 9 | .DS_Store -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # *.js -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // printWidth: 1000, 3 | tabWidth: 4, 4 | singleQuote: true, 5 | semi: false 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-colorpicker 2 | 3 | ## [LiveDemo](https://caohenghu.github.io/vue-colorpicker/) 4 | 5 | ![preview-dark](https://raw.githubusercontent.com/caohenghu/vue-colorpicker/master/src/img/preview-dark.jpg) 6 | ![preview-light](https://raw.githubusercontent.com/caohenghu/vue-colorpicker/master/src/img/preview-light.jpg) 7 | 8 | ## Install 9 | 10 | ```bash 11 | $ npm install @caohenghu/vue-colorpicker 12 | ``` 13 | 14 | ```bash 15 | $ yarn add @caohenghu/vue-colorpicker 16 | ``` 17 | 18 | ```bash 19 | $ pnpm add @caohenghu/vue-colorpicker 20 | ``` 21 | 22 | ## Example 23 | 24 | ```html 25 | 38 | 39 | 71 | ``` 72 | 73 | ## Options 74 | 75 | | Name | Type | Default | Description | 76 | | ------------------ | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | 77 | | theme | String | `dark` | `dark` or `light` | 78 | | color | String | `#000000` | `rgba` or `hex` | 79 | | colors-default | Array | `['#000000', '#FFFFFF', '#FF1900', '#F47365', '#FFB243', '#FFE623', '#6EFF2A', '#1BC7B1', '#00BEFF', '#2E81FF', '#5D61FF', '#FF89CF', '#FC3CAD', '#BF3DCE', '#8E00A7', 'rgba(0,0,0,0)']` | like `['#ff00ff', '#0f0f0f', ...]` | 80 | | colors-history-key | String | `vue-colorpicker-history` | 81 | | sucker-hide | Boolean | `true` | `true` or `false` | 82 | | sucker-canvas | HTMLCanvasElement | `null` | like `document.createElement('canvas')` | 83 | | sucker-area | Array | `[]` | like `[x1, y1, x2, y2]` | 84 | 85 | `color` is one-way data flow, so you can't use `v-model`. why? because you'll listen `changeColor` event do more things, so i think it's not necessary here. 86 | 87 | ## Events 88 | 89 | | Name | Type | Args | Description | 90 | | ----------- | -------- | ------ | ------------------------------- | 91 | | changeColor | Function | color | `{ rgba: {}, hsv: {}, hex: ''}` | 92 | | openSucker | Function | isOpen | `true` or `false` | 93 | 94 | if you want use sucker, then `openSucker`, `sucker-hide`, `sucker-canvas`, `sucker-area` is necessary. when you click sucker button, you can click it again or press key of `esc` to exit. 95 | 96 | ## Donate 97 | 98 | If this project has helped you, 99 | please have a cup of coffee for the author. 100 | 101 | [Donate with PayPal](https://paypal.me/caohenghu?country.x=C2&locale.x=zh_XC) 104 | Donate with wechat 107 | -------------------------------------------------------------------------------- /build/gh.js: -------------------------------------------------------------------------------- 1 | const ghpages = require('gh-pages') 2 | 3 | ghpages.publish('dist') -------------------------------------------------------------------------------- /demo/App.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 113 | 114 | 191 | -------------------------------------------------------------------------------- /demo/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caohenghu/vue-colorpicker/50c21d1aa53d0ad7795e204c8348a1a2be2deab5/demo/cover.jpg -------------------------------------------------------------------------------- /demo/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | vue-colorpicker 8 | 18 | 19 | 20 | 21 |
22 | <% for (lib of htmlWebpackPlugin.options.libs) { %> <% if (lib.isAsync) 23 | { %> 24 | 25 | <% } else { %> 26 | 27 | <% } %> <% } %> 28 | 29 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | import App from './App.vue' 2 | 3 | new Vue({ 4 | el: '#app', 5 | render: h => h(App) 6 | }) 7 | -------------------------------------------------------------------------------- /env/gh.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | host: { 3 | cdn: 'caohenghu.github.io' 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /env/pro.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | host: { 3 | cdn: 'caohenghu.github.io' 4 | }, 5 | plugin: [ 6 | '//cdn.bootcss.com/vue/2.5.13/vue.runtime.min.js' 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@caohenghu/vue-colorpicker", 3 | "version": "1.2.4", 4 | "description": "Colorpicker of Vue Components", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "hot": "webpack-dev-server --env.config local --env.local", 8 | "pro": "rm -rf dist && webpack --env.config pro --env.pro", 9 | "gh": "node build/gh.js" 10 | }, 11 | "keywords": [ 12 | "vue", 13 | "colorpicker", 14 | "color" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/caohenghu/vue-colorpicker" 19 | }, 20 | "author": "caohenghu ", 21 | "license": "MIT", 22 | "files": [ 23 | "src" 24 | ], 25 | "devDependencies": { 26 | "autoprefixer": "^9.7.4", 27 | "css-loader": "^3.2.0", 28 | "eslint": "^6.2.2", 29 | "eslint-plugin-vue": "^6.1.2", 30 | "file-loader": "^4.2.0", 31 | "gh-pages": "^2.0.1", 32 | "html-webpack-plugin": "^3.2.0", 33 | "mini-css-extract-plugin": "^0.9.0", 34 | "node-sass": "^4.11.0", 35 | "optimize-css-assets-webpack-plugin": "^5.0.3", 36 | "postcss-loader": "^3.0.0", 37 | "sass-loader": "^7.1.0", 38 | "url-loader": "^2.1.0", 39 | "vue-loader": "^15.5.1", 40 | "vue-template-compiler": "^2.5.22", 41 | "webpack": "^4.41.6", 42 | "webpack-cli": "^3.2.1", 43 | "webpack-dev-server": "^3.1.14" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require('autoprefixer')] 3 | } 4 | -------------------------------------------------------------------------------- /src/color/Alpha.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 106 | 107 | 124 | -------------------------------------------------------------------------------- /src/color/Box.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 37 | 38 | 65 | -------------------------------------------------------------------------------- /src/color/Colors.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 96 | 97 | 137 | -------------------------------------------------------------------------------- /src/color/Hue.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 106 | 107 | -------------------------------------------------------------------------------- /src/color/Index.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 258 | 259 | 298 | -------------------------------------------------------------------------------- /src/color/Preview.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 57 | -------------------------------------------------------------------------------- /src/color/Saturation.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 112 | 113 | -------------------------------------------------------------------------------- /src/color/Sucker.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 163 | 164 | 177 | -------------------------------------------------------------------------------- /src/color/mixin.js: -------------------------------------------------------------------------------- 1 | export default { 2 | methods: { 3 | setColorValue(color) { 4 | let rgba = { r: 0, g: 0, b: 0, a: 1 } 5 | if (/#/.test(color)) { 6 | rgba = this.hex2rgb(color) 7 | } else if (/rgb/.test(color)) { 8 | rgba = this.rgb2rgba(color) 9 | } else if (typeof color === 'string') { 10 | rgba = this.rgb2rgba(`rgba(${color})`) 11 | } else if (Object.prototype.toString.call(color) === '[object Object]') { 12 | rgba = color 13 | } 14 | const { r, g, b, a } = rgba 15 | const { h, s, v } = this.rgb2hsv(rgba) 16 | return { r, g, b, a: a === undefined ? 1 : a, h, s, v } 17 | }, 18 | createAlphaSquare(size) { 19 | const canvas = document.createElement('canvas') 20 | const ctx = canvas.getContext('2d') 21 | const doubleSize = size * 2 22 | canvas.width = doubleSize 23 | canvas.height = doubleSize 24 | 25 | ctx.fillStyle = '#ffffff' 26 | ctx.fillRect(0, 0, doubleSize, doubleSize) 27 | ctx.fillStyle = '#ccd5db' 28 | ctx.fillRect(0, 0, size, size) 29 | ctx.fillRect(size, size, size, size) 30 | 31 | return canvas 32 | }, 33 | createLinearGradient(direction, ctx, width, height, color1, color2) { 34 | // l 横向 p 纵向 35 | const isL = direction === 'l' 36 | const gradient = ctx.createLinearGradient(0, 0, isL ? width : 0, isL ? 0 : height) 37 | gradient.addColorStop(0.01, color1) 38 | gradient.addColorStop(0.99, color2) 39 | ctx.fillStyle = gradient 40 | ctx.fillRect(0, 0, width, height) 41 | }, 42 | rgb2hex({ r, g, b }, toUpper) { 43 | const change = val => ('0' + Number(val).toString(16)).slice(-2) 44 | const color = `#${change(r)}${change(g)}${change(b)}` 45 | return toUpper ? color.toUpperCase() : color 46 | }, 47 | hex2rgb(hex) { 48 | hex = hex.slice(1) 49 | const change = val => parseInt(val, 16) || 0 // 避免NaN的情况 50 | return { 51 | r: change(hex.slice(0, 2)), 52 | g: change(hex.slice(2, 4)), 53 | b: change(hex.slice(4, 6)) 54 | } 55 | }, 56 | rgb2rgba(rgba) { 57 | if (typeof rgba === 'string') { 58 | rgba = (/rgba?\((.*?)\)/.exec(rgba) || ['', '0,0,0,1'])[1].split(',') 59 | return { 60 | r: Number(rgba[0]) || 0, 61 | g: Number(rgba[1]) || 0, 62 | b: Number(rgba[2]) || 0, 63 | a: Number(rgba[3] ? rgba[3] : 1) // 避免为0的情况 64 | } 65 | } else { 66 | return rgba 67 | } 68 | }, 69 | rgb2hsv({ r, g, b }) { 70 | r = r / 255 71 | g = g / 255 72 | b = b / 255 73 | const max = Math.max(r, g, b) 74 | const min = Math.min(r, g, b) 75 | const delta = max - min 76 | let h = 0 77 | if (max === min) { 78 | h = 0 79 | } else if (max === r) { 80 | if (g >= b) { 81 | h = (60 * (g - b)) / delta 82 | } else { 83 | h = (60 * (g - b)) / delta + 360 84 | } 85 | } else if (max === g) { 86 | h = (60 * (b - r)) / delta + 120 87 | } else if (max === b) { 88 | h = (60 * (r - g)) / delta + 240 89 | } 90 | h = Math.floor(h) 91 | let s = parseFloat((max === 0 ? 0 : 1 - min / max).toFixed(2)) 92 | let v = parseFloat(max.toFixed(2)) 93 | return { h, s, v } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/img/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caohenghu/vue-colorpicker/50c21d1aa53d0ad7795e204c8348a1a2be2deab5/src/img/paypal.png -------------------------------------------------------------------------------- /src/img/preview-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caohenghu/vue-colorpicker/50c21d1aa53d0ad7795e204c8348a1a2be2deab5/src/img/preview-dark.jpg -------------------------------------------------------------------------------- /src/img/preview-light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caohenghu/vue-colorpicker/50c21d1aa53d0ad7795e204c8348a1a2be2deab5/src/img/preview-light.jpg -------------------------------------------------------------------------------- /src/img/sucker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caohenghu/vue-colorpicker/50c21d1aa53d0ad7795e204c8348a1a2be2deab5/src/img/sucker.png -------------------------------------------------------------------------------- /src/img/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caohenghu/vue-colorpicker/50c21d1aa53d0ad7795e204c8348a1a2be2deab5/src/img/wechat.jpg -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Color from './color/Index.vue' 2 | export default Color 3 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const HtmlWebpackPlugin = require('html-webpack-plugin') 3 | const VueLoaderPlugin = require('vue-loader/lib/plugin') 4 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') 5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 6 | const path = require('path') 7 | 8 | const minify = { 9 | minifyCSS: true, 10 | minifyJS: true, 11 | removeComments: true, // 删除HTML中的注释 12 | collapseWhitespace: true, // 删除空白符与换行符 13 | collapseBooleanAttributes: true, // 省略布尔属性的值 ==> 14 | removeEmptyAttributes: true, // 删除所有空格作属性值 ==> 15 | removeScriptTypeAttributes: true, // 删除script上的type 16 | removeStyleLinkTypeAttributes: true // 删除style上的type 17 | } 18 | 19 | module.exports = options => { 20 | const env = require('./env/' + options.config + '.js') 21 | const plugin = require('./env/pro.js').plugin 22 | const port = env.port || 5555 23 | const isLocal = options.local 24 | const libs = [] 25 | 26 | const rules = [ 27 | { 28 | test: /\.vue$/, 29 | use: 'vue-loader' 30 | }, 31 | // { 32 | // test: /\.js$/, 33 | // use: 'babel-loader' 34 | // }, 35 | { 36 | test: /\.scss$/, 37 | use: [ 38 | isLocal ? 'vue-style-loader' : MiniCssExtractPlugin.loader, 39 | 'css-loader', 40 | 'postcss-loader', 41 | 'sass-loader' 42 | ] 43 | }, 44 | { 45 | test: /\.(png|jpg|gif|svg|ico)$/, 46 | use: [ 47 | { 48 | loader: 'url-loader', 49 | options: { 50 | name: 'images/[name]-[hash:8].[ext]', 51 | limit: 1000 52 | } 53 | } 54 | ] 55 | } 56 | ] 57 | const plugins = [ 58 | new VueLoaderPlugin(), 59 | new HtmlWebpackPlugin({ 60 | template: './demo/index.ejs', 61 | filename: 'index.html', 62 | minify, 63 | libs 64 | }) 65 | ] 66 | 67 | if (isLocal) { 68 | plugins.push(new webpack.HotModuleReplacementPlugin()) 69 | } else { 70 | plugins.push( 71 | new OptimizeCssAssetsPlugin(), 72 | new MiniCssExtractPlugin({ 73 | filename: 'css/[name]-[hash:8].min.css' 74 | }) 75 | ) 76 | } 77 | 78 | // 如果是本地开发,使用未压缩的插件 79 | if (isLocal) { 80 | plugin.forEach((item, i) => { 81 | plugin[i] = item.replace('.min', '') 82 | }) 83 | } 84 | plugin.forEach(item => { 85 | libs.push({ 86 | url: item, 87 | isAsync: false 88 | }) 89 | }) 90 | 91 | return { 92 | mode: isLocal ? 'development' : 'production', 93 | entry: { 94 | app: './demo' 95 | }, 96 | output: { 97 | publicPath: isLocal 98 | ? '' 99 | : '//' + env.host.cdn + '/vue-colorpicker/', 100 | path: path.resolve('dist'), 101 | filename: isLocal ? 'js/[name].js' : 'js/[name]-[hash:8].js' 102 | }, 103 | module: { 104 | rules 105 | }, 106 | plugins, 107 | externals: { 108 | vue: 'Vue' 109 | }, 110 | devtool: options.pro ? false : 'source-map', 111 | devServer: { 112 | hot: true, 113 | port 114 | } 115 | } 116 | } 117 | --------------------------------------------------------------------------------