├── .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 | 
6 | 
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 |
26 |
27 |
36 |
37 |
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 | [
](https://paypal.me/caohenghu?country.x=C2&locale.x=zh_XC)
104 |
107 |
--------------------------------------------------------------------------------
/build/gh.js:
--------------------------------------------------------------------------------
1 | const ghpages = require('gh-pages')
2 |
3 | ghpages.publish('dist')
--------------------------------------------------------------------------------
/demo/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | vue-colorpicker
6 |
7 |
8 |
17 |
![]()
18 |
19 |
20 |
28 |
34 |
35 |
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 |
2 |
12 |
13 |
14 |
106 |
107 |
124 |
--------------------------------------------------------------------------------
/src/color/Box.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ name }}
5 |
6 |
10 |
11 |
12 |
13 |
37 |
38 |
65 |
--------------------------------------------------------------------------------
/src/color/Colors.vue:
--------------------------------------------------------------------------------
1 |
2 |
41 |
42 |
43 |
96 |
97 |
137 |
--------------------------------------------------------------------------------
/src/color/Hue.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
106 |
107 |
--------------------------------------------------------------------------------
/src/color/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
31 |
48 |
53 |
58 |
64 |
65 |
66 |
67 |
258 |
259 |
298 |
--------------------------------------------------------------------------------
/src/color/Preview.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
57 |
--------------------------------------------------------------------------------
/src/color/Saturation.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
112 |
113 |
--------------------------------------------------------------------------------
/src/color/Sucker.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
47 |
48 |
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 |
--------------------------------------------------------------------------------