├── icon.ico ├── icon.png ├── icon.icns ├── gifs ├── fast.gif ├── use.gif ├── alpha.gif └── start.gif ├── app ├── assets │ ├── image │ │ ├── icon.ico │ │ ├── logo.png │ │ ├── change.png │ │ ├── delete.png │ │ ├── icon_app.png │ │ ├── icon_tray.png │ │ ├── icon_default.png │ │ └── icon_whilte.png │ ├── pick.html │ └── index.html ├── store.js ├── renderer │ ├── store │ │ └── index.js │ ├── indexPage.js │ ├── home │ │ ├── appExtends.js │ │ ├── colorHistory.js │ │ ├── keyboard.js │ │ └── shortKey.js │ ├── css │ │ ├── pick.css │ │ └── home.css │ └── pickPage.js ├── db.js ├── menur.js └── main.js ├── icon.iconset ├── icon_16x16.png ├── icon_32x32.png ├── icon_128x128.png ├── icon_16x16@2x.png ├── icon_256x256.png ├── icon_32x32@2x.png ├── icon_512x512.png ├── icon_128x128@2x.png ├── icon_256x256@2x.png └── icon_512x512@2x.png ├── .gitignore ├── package.json ├── LICENSE.md └── README.md /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/icon.ico -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/icon.png -------------------------------------------------------------------------------- /icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/icon.icns -------------------------------------------------------------------------------- /gifs/fast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/gifs/fast.gif -------------------------------------------------------------------------------- /gifs/use.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/gifs/use.gif -------------------------------------------------------------------------------- /gifs/alpha.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/gifs/alpha.gif -------------------------------------------------------------------------------- /gifs/start.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/gifs/start.gif -------------------------------------------------------------------------------- /app/assets/image/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/app/assets/image/icon.ico -------------------------------------------------------------------------------- /app/assets/image/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/app/assets/image/logo.png -------------------------------------------------------------------------------- /app/assets/image/change.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/app/assets/image/change.png -------------------------------------------------------------------------------- /app/assets/image/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/app/assets/image/delete.png -------------------------------------------------------------------------------- /icon.iconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/icon.iconset/icon_16x16.png -------------------------------------------------------------------------------- /icon.iconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/icon.iconset/icon_32x32.png -------------------------------------------------------------------------------- /app/assets/image/icon_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/app/assets/image/icon_app.png -------------------------------------------------------------------------------- /app/assets/image/icon_tray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/app/assets/image/icon_tray.png -------------------------------------------------------------------------------- /icon.iconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/icon.iconset/icon_128x128.png -------------------------------------------------------------------------------- /icon.iconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/icon.iconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /icon.iconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/icon.iconset/icon_256x256.png -------------------------------------------------------------------------------- /icon.iconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/icon.iconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /icon.iconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/icon.iconset/icon_512x512.png -------------------------------------------------------------------------------- /app/assets/image/icon_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/app/assets/image/icon_default.png -------------------------------------------------------------------------------- /app/assets/image/icon_whilte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/app/assets/image/icon_whilte.png -------------------------------------------------------------------------------- /icon.iconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/icon.iconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /icon.iconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/icon.iconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /icon.iconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthurYung/ColorPoint/HEAD/icon.iconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /.idea/ 3 | /.cache/ 4 | /db.json/ 5 | yarn.lock 6 | yarn-error.log 7 | /ignore/ 8 | /pkg/ 9 | package-lock.json 10 | -------------------------------------------------------------------------------- /app/store.js: -------------------------------------------------------------------------------- 1 | const { getShort, getColor } = require('./db') 2 | 3 | const DEFAULTE_KEYS = 'DEFAULTE_KEYS' 4 | const HISTORY_COLOR = 'HISTORY_COLOR' 5 | 6 | const actions = [ 7 | { 8 | type: DEFAULTE_KEYS, 9 | default: getShort() 10 | }, 11 | { 12 | type: HISTORY_COLOR, 13 | default: getColor() 14 | } 15 | ] 16 | 17 | 18 | 19 | module.exports = { actions, DEFAULTE_KEYS, HISTORY_COLOR } -------------------------------------------------------------------------------- /app/renderer/store/index.js: -------------------------------------------------------------------------------- 1 | const { remote, ipcRenderer } = require( "electron" ); 2 | 3 | const mutation = (action)=>{ 4 | ipcRenderer.send('connct-store-context', action) 5 | } 6 | 7 | const getter = (key) => { 8 | return remote.getGlobal( "Store" )[key] 9 | } 10 | 11 | const connect = (apps = []) => { 12 | ipcRenderer.on('connct-store-provider', (event, action) => { 13 | apps.forEach(app => { 14 | if (app.subs.includes(action.type)) { 15 | app.object.dispatch(action) 16 | } 17 | }) 18 | }) 19 | } 20 | 21 | module.exports = {mutation, getter, connect} -------------------------------------------------------------------------------- /app/assets/pick.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 17 | 18 | 19 | 20 |
21 | 24 | 25 | -------------------------------------------------------------------------------- /app/renderer/indexPage.js: -------------------------------------------------------------------------------- 1 | const { connect } = require('./store/index.js') 2 | const { ipcRenderer } = require( "electron" ); 3 | const Keyboard = require('./home/keyboard') 4 | const ColorHistory = require('./home/colorHistory') 5 | const size = {width: screen.availWidth, height: screen.availHeight} 6 | 7 | const startCapture = ()=> { 8 | ipcRenderer.send('start-point', size) 9 | } 10 | 11 | ipcRenderer.on('shortcut-show', startCapture) 12 | 13 | connect([{ 14 | subs: ['DEFAULTE_KEYS'], 15 | object: new Keyboard({ 16 | el: document.querySelector('.mixin-keyboard'), 17 | start: startCapture 18 | }) 19 | }, { 20 | subs: ['HISTORY_COLOR'], 21 | object: new ColorHistory({ 22 | el: document.querySelector('.mixin-colors') 23 | }) 24 | }]) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "color-point", 3 | "version": "1.0.3", 4 | "private": true, 5 | "description": "A colour picking app based on Electron", 6 | "main": "./app/main.js", 7 | "scripts": { 8 | "start": "cross-env NODE_ENV=dev electron .", 9 | "build:win": "electron-packager . Color-Point --platform=win32 --icon=./icon.ico --overwrite", 10 | "build:mac": "electron-packager . Color-Point --platform=darwin --icon=./icon.icns --overwrite --darwinDarkModeSupport=true" 11 | }, 12 | "repository": "https://github.com/electron/electron-quick-start", 13 | "keywords": [ 14 | "Electron", 15 | "Color", 16 | "Picker", 17 | "App" 18 | ], 19 | "author": "Bruce.Au", 20 | "license": "TIM", 21 | "devDependencies": { 22 | "cross-env": "^5.2.0", 23 | "electron": "^4.0.7", 24 | "electron-debug": "^1.5.0", 25 | "electron-packager": "^13.1.1" 26 | }, 27 | "dependencies": { 28 | "desktop-screenshot": "^0.1.1", 29 | "lowdb": "^1.0.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Burce.Au 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 | -------------------------------------------------------------------------------- /app/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 17 | 18 | 19 |
20 |
21 | 22 |
23 | v1.0.0 24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 35 |
36 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/db.js: -------------------------------------------------------------------------------- 1 | const { app } = require('electron') 2 | const low = require('lowdb') 3 | const { resolve } = require('path') 4 | const FileSync = require('lowdb/adapters/FileSync') 5 | const json = resolve(app.getPath('userData'), 'db.json') 6 | 7 | const adapter = new FileSync(json) 8 | const db = low(adapter) 9 | 10 | const isMac = process.platform === 'darwin' 11 | const defaultShort = `${isMac ? 'Command' : 'Control'}+Alt+G` 12 | 13 | db.defaults({ shortcut: defaultShort, colors: [] }).write() 14 | 15 | exports.changeShort = key => { 16 | db.set('shortcut', key).write() 17 | } 18 | 19 | exports.getShort = () => { 20 | return db.get('shortcut').value() 21 | } 22 | 23 | exports.pushColor = color => { 24 | if (Array.isArray(color) && color.length === 0) { 25 | return db.set('colors', []).write() 26 | } 27 | const _COLOR_ = db.get('colors') 28 | const size = _COLOR_.size().value() 29 | if (size >= 9) { 30 | _COLOR_.remove(_COLOR_.first().value()).write() 31 | } 32 | _COLOR_.push({ 33 | ID: Date.now().toString(), 34 | value: color 35 | }).write() 36 | } 37 | 38 | exports.getColor = () => { 39 | return db.get('colors').value().map(color => { 40 | return color.value 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /app/renderer/home/appExtends.js: -------------------------------------------------------------------------------- 1 | class AppExtend { 2 | find(query) { 3 | return this.view.querySelector(query) 4 | } 5 | bind() { 6 | this.view.addEventListener('click', event => { 7 | let target = event.target 8 | while(target.parentNode) { 9 | if (target === this.view || target === document.body) { 10 | break 11 | } 12 | if (target.dataset.click) { 13 | this.bindEvent().clicks[target.dataset.click](event) 14 | break 15 | } 16 | target = target.parentNode 17 | } 18 | }) 19 | this.view.addEventListener('mouseenter', () => { 20 | this.view.classList.add('app-hover') 21 | }) 22 | this.view.addEventListener('mouseleave', () => { 23 | this.view.classList.remove('app-hover') 24 | }) 25 | } 26 | bindOthre() { 27 | const focusEvent = this.bindEvent().focus 28 | const blurEvent = this.bindEvent().blur 29 | Object.keys(focusEvent).forEach(key => { 30 | this.find(`[data-focus=${key}]`) 31 | && this.find(`[data-focus=${key}]`).addEventListener('focus', focusEvent[key]) 32 | }) 33 | Object.keys(blurEvent).forEach(key => { 34 | this.find(`[data-blur=${key}]`) 35 | && this.find(`[data-blur=${key}]`).addEventListener('blur', blurEvent[key]) 36 | }) 37 | } 38 | } 39 | 40 | module.exports = AppExtend -------------------------------------------------------------------------------- /app/menur.js: -------------------------------------------------------------------------------- 1 | const { Menu, app, BrowserWindow } = require('electron') 2 | const { platform } = require('process') 3 | const isMac = platform === 'darwin' 4 | 5 | 6 | exports.menuBuild = (main, start) => { 7 | trayTemplate = [ 8 | { 9 | label: 'Start', 10 | click: start 11 | }, 12 | { 13 | label: 'Show', 14 | click () { 15 | main.show() 16 | } 17 | }, 18 | { 19 | label: 'Help', 20 | click() { 21 | console.log(BrowserWindow.getAllWindows()) 22 | 23 | } 24 | }, 25 | { 26 | label: 'Quit', 27 | click () { 28 | main.destroy() 29 | } 30 | } 31 | ] 32 | return Menu.buildFromTemplate(trayTemplate) 33 | } 34 | 35 | exports.createMenu = (main) => { 36 | const template = [ 37 | { 38 | label: 'Color Point', 39 | submenu: [ 40 | { 41 | label: 'Help', 42 | role: 'help' 43 | }, 44 | { 45 | label: 'Quit', 46 | click () { 47 | main.destroy() 48 | } 49 | } 50 | ] 51 | }, 52 | { 53 | label: 'Help', 54 | role: 'help' 55 | }, 56 | { 57 | label: 'Quit', 58 | click () { 59 | main.destroy() 60 | } 61 | } 62 | ] 63 | const menu = Menu.buildFromTemplate(template) 64 | if (isMac) { 65 | Menu.setApplicationMenu(menu) 66 | } else { 67 | Menu.setApplicationMenu(null) 68 | } 69 | } 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Color Point 2 | 3 | > 桌面吸色工具 4 | 5 |

6 | 7 |

8 |

9 | 10 | 11 | 12 |

13 | 14 | ## 应用说明 15 | 16 | **一款基于Electron开发的桌面应用** 17 | 18 | 目前支持: 19 | - `RGBA色值` 20 | - `十六进制色值` 21 | - `透明度计算` 22 | - `自定义快捷键` 23 | - `取色记录` 24 | 25 | 26 | ## 下载安装 27 | 28 | [Windows](https://github.com/ArthurYung/ColorPoint/releases/download/1.0.7/Color-Point-win32-x64.7z) 29 | [MacOS](https://github.com/ArthurYung/ColorPoint/releases/download/1.0.7/Color-Point-darwin-x64.zip) 30 | 31 | 32 | ## 开始使用 33 | 34 | ### 可在主界面自定义快捷键 . 35 | ![start](https://raw.githubusercontent.com/ArthurYung/ColorPoint/master/gifs/start.gif) 36 | 37 | ### 点击开始按钮或使用快捷键开始吸取颜色,吸取颜色后会自动复制到剪切板 . 38 | ![use](https://raw.githubusercontent.com/ArthurYung/ColorPoint/master/gifs/use.gif) 39 | 40 | ### 系统托盘內也有开始按钮 . 41 | ![fast](https://raw.githubusercontent.com/ArthurYung/ColorPoint/master/gifs/fast.gif) 42 | 43 | ### 吸取颜色时左下角菜单可切换色值类型 . 44 | 当切换透明度模式时,你可以得到一个带有指定透明度的色值。 45 | **这个色值叠加到所选背景色之后的颜色和当前屏幕所选颜色相同。 即(底色rgba1 & 透明度色值rgba2) = 屏幕色rgba3** 46 | 右键可将鼠标所指颜色设置为透明度计算的底色,点击透明度左侧色块可切换底色为纯黑/纯白。 47 | ![alpha](https://raw.githubusercontent.com/ArthurYung/ColorPoint/master/gifs/alpha.gif) 48 | 49 | ## 开发说明 50 | 51 | 下载项目并安装依赖 52 | 53 | ```bash 54 | git clone https://github.com/ArthurYung/ColorPoint.git 55 | cd colorPoint && npm install 56 | ``` 57 | 58 | 启动项目 59 | ```bash 60 | npm start 61 | ``` 62 | 63 | 打包生产 64 | ```bash 65 | npm run build:mac 66 | ``` 67 | **未使用热更新,如有需要可自行实现** 68 | 69 | 70 | ## License 71 | 72 | [MIT](LICENSE.md) 73 | -------------------------------------------------------------------------------- /app/renderer/home/colorHistory.js: -------------------------------------------------------------------------------- 1 | const { clipboard, ipcRenderer } = require( "electron" ); 2 | const AppExtend = require('./appExtends') 3 | const { getter, mutation } = require('../store') 4 | class ColorHistory extends AppExtend { 5 | constructor(opt) { 6 | super() 7 | this.view = opt.el || document.body 8 | this.init() 9 | } 10 | init () { 11 | this.proxy() 12 | this.bind() 13 | this._render() 14 | this.colors.render = getter('HISTORY_COLOR') 15 | } 16 | proxy () { 17 | let self = this 18 | this.colors = new Proxy({}, { 19 | set(proxy, key, value) { 20 | proxy[key] = value 21 | console.log(value) 22 | self.colorRender(value) 23 | return true 24 | }, 25 | get(proxy, key) { 26 | return proxy[key] 27 | } 28 | }) 29 | } 30 | 31 | dispatch() { 32 | this.colors.render = getter('HISTORY_COLOR') 33 | } 34 | 35 | _render() { 36 | this.view.innerHTML = this.render() 37 | } 38 | 39 | bindEvent() { 40 | let self = this 41 | return { 42 | clicks: { 43 | handleClick () { 44 | mutation({ 45 | type: 'HISTORY_COLOR', 46 | payload: [] 47 | }) 48 | }, 49 | chooseColor (event) { 50 | const color = event.target.dataset.color 51 | if (color) { 52 | event.stopPropagation() 53 | clipboard.writeText(color) 54 | ipcRenderer.send('show-notification') 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | colorRender(colors) { 62 | let LISTS = '' 63 | colors.forEach(color => { 64 | LISTS += `` 65 | }) 66 | this.find('.color-view') && (this.find('.color-view').innerHTML = LISTS) 67 | } 68 | 69 | render() { 70 | return ` 71 |
72 |
73 | History colors 74 |
75 |
76 | 77 |
` 78 | } 79 | } 80 | 81 | module.exports = ColorHistory -------------------------------------------------------------------------------- /app/renderer/home/keyboard.js: -------------------------------------------------------------------------------- 1 | const ShortKeys = require('./shortKey') 2 | const AppExtend = require('./appExtends') 3 | const { getter, mutation } = require('../store') 4 | class KeyboardApp extends AppExtend { 5 | constructor(opt) { 6 | super() 7 | this.view = opt.el || document.body 8 | this.start = opt.start || new Function() 9 | this.shortKey = new ShortKeys() 10 | this.keyboard = getter('DEFAULTE_KEYS') 11 | this.buttonText = "Start Picking Colors" 12 | this.type = 1 13 | this.init() 14 | } 15 | init() { 16 | this.bind() 17 | this._render() 18 | this.shortKey.onkeypress(value => { 19 | if (value === '') { 20 | value = 'Shortcut keys is null' 21 | } 22 | this.keyboard = value 23 | this.find('.keyboard-view') && (this.find('.keyboard-view').value = value) 24 | }) 25 | } 26 | 27 | _render() { 28 | this.view.innerHTML = this.render() 29 | this.bindOthre() 30 | } 31 | switch() { 32 | this.type = this.type === 1 ? 2 : 1 33 | this._render() 34 | } 35 | bindEvent() { 36 | let self = this 37 | return { 38 | clicks: { 39 | handleClick () { 40 | self.switch() 41 | }, 42 | handleStart () { 43 | self.start() 44 | } 45 | }, 46 | focus: { 47 | handleFocus (e) { 48 | e.target.parentNode.classList.add('reset-input-focus') 49 | self.shortKey.onFocus() 50 | } 51 | }, 52 | blur: { 53 | handleBlur (e) { 54 | e.target.parentNode.classList.remove('reset-input-focus') 55 | self.shortKey.onBlur() 56 | mutation({ 57 | type: 'DEFAULTE_KEYS', 58 | payload: self.keyboard 59 | }) 60 | } 61 | } 62 | } 63 | } 64 | dispatch(action) { 65 | this.keyboard = action.payload 66 | } 67 | render() { 68 | if (this.type === 1) { 69 | return ` 70 |
71 |
72 |
${this.buttonText}
73 |
${this.keyboard}
74 |
75 | 76 |
` 77 | } else { 78 | return ` 79 |
80 |
81 | 82 |
83 | 84 |
` 85 | } 86 | } 87 | } 88 | 89 | module.exports = KeyboardApp -------------------------------------------------------------------------------- /app/renderer/css/pick.css: -------------------------------------------------------------------------------- 1 | body, html{ 2 | overflow: hidden; 3 | background-color: rgba(0, 0, 0, 0); 4 | position: relative; 5 | } 6 | 7 | canvas { 8 | position: absolute; 9 | left: 0; 10 | bottom: 0 11 | } 12 | 13 | .clip-view { 14 | position: absolute; 15 | top: 0; 16 | left: 0; 17 | bottom: auto 18 | } 19 | .clip-view canvas { 20 | border-radius: 50%; 21 | position: absolute; 22 | top: 0; 23 | left: 0; 24 | border: 2px solid #000000; 25 | } 26 | 27 | .clip-view span { 28 | display: inline-block; 29 | position: absolute; 30 | top: 110%; 31 | left: 50%; 32 | transform: translateX(-50%); 33 | background-color: rgba(0, 0, 0, .4); 34 | line-height: 20px; 35 | font-size: 10px; 36 | color: #ffffff; 37 | padding: 0 4px; 38 | white-space:nowrap; 39 | } 40 | 41 | 42 | .menu-view { 43 | position: absolute; 44 | left: 0; 45 | bottom: 10%; 46 | background-color: rgba(255,255,255,.9); 47 | border-radius: 0 12px 12px 0; 48 | width: 160px; 49 | height: 110px; 50 | padding: 10px 0; 51 | cursor: pointer; 52 | transition: transform .4s ease-out 53 | } 54 | 55 | .hide-view { 56 | transform: translate(-120px, 0) 57 | } 58 | 59 | .menu-view a { 60 | background: none; 61 | border:none; 62 | display: block; 63 | position: absolute; 64 | width: 16px; 65 | height: 20px; 66 | right: 8px; 67 | top: 48px; 68 | text-align: center; 69 | font-size: 22px; 70 | color: #868686 71 | } 72 | 73 | .menu-view a:hover { 74 | color: #292929 75 | } 76 | 77 | .menu-view a.menu-show::before { 78 | content: '‹' 79 | } 80 | 81 | .menu-view a.menu-hide::before { 82 | content: '›' 83 | } 84 | 85 | .canvas-label { 86 | padding: 0 14px; 87 | line-height: 30px; 88 | font-size: 14px; 89 | width: 70% 90 | } 91 | 92 | .canvas-label:hover>span:first-child { 93 | border-color: #3a3a3a 94 | } 95 | 96 | .canvas-label>span:first-child { 97 | position: relative; 98 | display: block; 99 | width: 18px; 100 | height: 18px; 101 | border-radius: 50%; 102 | border: 1px solid #9a9a9a; 103 | margin-right: 8px; 104 | margin-top: 4px; 105 | float: left; 106 | } 107 | 108 | .canvas-label > span.checked-value::before { 109 | content: ''; 110 | display: block; 111 | position: absolute; 112 | top: 2px; 113 | left: 2px; 114 | width: 14px; 115 | height: 14px; 116 | border-radius: 50%; 117 | background-color: #9343e9 118 | } 119 | 120 | input[type=range] { 121 | -webkit-appearance: none; 122 | width: 80px; 123 | height: 2px; 124 | border-radius: 2px; /*这个属性设置使填充进度条时的图形为圆角*/ 125 | background-color: #dedede 126 | } 127 | input[type=range]::-webkit-slider-thumb { 128 | -webkit-appearance: none; 129 | appearance: none; /*//这三个是去掉滑块原有的默认样式,划重点!!*/ 130 | -webkit-box-shadow:0 0 2px ; /*设置滑块的阴影*/ 131 | width: 12px; 132 | height: 12px; 133 | background: #fff; 134 | border-radius: 50%; 135 | border: 2px solid #9343e9; 136 | } 137 | .range-box { 138 | position: relative; 139 | opacity: 0.2; 140 | } 141 | 142 | .is-alpha { 143 | opacity: 1; 144 | } 145 | 146 | .range-box i{ 147 | display: inline-block; 148 | width: 10px; 149 | height: 10px; 150 | border: 1px solid #acacac; 151 | margin: 0 8px 0 16px; 152 | } 153 | .range-box input { 154 | position: absolute; 155 | top: 10px; 156 | left: 40px; 157 | } 158 | 159 | .range-box span { 160 | margin-left: 90px; 161 | font-size: 12px 162 | } -------------------------------------------------------------------------------- /app/renderer/css/home.css: -------------------------------------------------------------------------------- 1 | .main-logo { 2 | display: block; 3 | margin: 30px auto 0 auto 4 | } 5 | 6 | .main-title { 7 | width: 100%; 8 | text-align: center; 9 | line-height: 34px 10 | } 11 | 12 | .main-text { 13 | font-size: 16px; 14 | color: #696a77 15 | } 16 | 17 | .article-app { 18 | width: 100%; 19 | position: relative; 20 | } 21 | 22 | .mixin-keyboard { 23 | height: 44px; 24 | margin-top: 14px; 25 | } 26 | 27 | .start-view { 28 | width: 100%; 29 | height: 100%; 30 | position: relative; 31 | overflow: hidden; 32 | } 33 | 34 | .start-btn { 35 | position: relative; 36 | width: 236px; 37 | height: 44px; 38 | margin: 0 auto; 39 | padding: 0; 40 | border-radius: 22px; 41 | background-color: #9343e9; 42 | border:none; 43 | cursor: pointer; 44 | transition: background-color .3s ease-out 45 | } 46 | 47 | .button-text,.button-juder { 48 | position: absolute; 49 | width: 100%; 50 | text-align: center; 51 | transition: transform .3s ease-out; 52 | } 53 | 54 | .button-text { 55 | top: 0px; 56 | line-height: 44px; 57 | color: #fff; 58 | font-size: 18px; 59 | font-weight: bolder; 60 | } 61 | 62 | .button-juder { 63 | top: 44px; 64 | color: rgba(255,255,255,.5); 65 | font-size: 14px 66 | } 67 | 68 | .button { 69 | border: none; 70 | border-radius: 0; 71 | margin: 0; 72 | padding: 0; 73 | background-color: rgba(0,0,0,0) 74 | } 75 | 76 | .reset-key { 77 | display: block; 78 | visibility: hidden; 79 | position: absolute; 80 | right: 50px; 81 | top: 12px; 82 | transition: opacity .3s ease-out; 83 | opacity: 0; 84 | } 85 | 86 | .icon { 87 | display: block; 88 | width: 20px; 89 | height: 20px; 90 | background-position: center; 91 | background-repeat: no-repeat; 92 | background-size: contain; 93 | cursor: pointer; 94 | } 95 | 96 | .icon-reset { 97 | background-image: url('../../assets/image/change.png') 98 | } 99 | 100 | .start-btn:hover { 101 | background-color: #a158f0 102 | } 103 | 104 | .app-hover .button-text { 105 | transform: translate(0, -8px) 106 | } 107 | 108 | .app-hover .button-juder { 109 | transform: translate(0, -20px) 110 | } 111 | 112 | .app-hover .reset-key, .app-hover .delete-all { 113 | visibility: visible; 114 | opacity: 1; 115 | } 116 | 117 | .reset-input { 118 | position: relative; 119 | display: flex; 120 | width: 230px; 121 | height: 36px; 122 | background-color: rgba(255,255,255,.8); 123 | overflow: hidden; 124 | border-radius: 18px; 125 | margin: 4px auto 0 auto; 126 | justify-content: center; 127 | transition: background-color .3s ease-out 128 | } 129 | 130 | .reset-input-focus { 131 | background-color: rgba(255,255,255,1) 132 | } 133 | 134 | .keyboard-view { 135 | position: relative; 136 | width: 100%; 137 | margin: 0; 138 | padding: 0; 139 | border:none; 140 | background-color: rgba(0,0,0,0); 141 | font-size: 16px; 142 | font-weight: bolder; 143 | color: #9343e9; 144 | text-align: center; 145 | } 146 | 147 | .delete-all { 148 | visibility: hidden; 149 | position: absolute; 150 | top: 8px; 151 | right: 120px; 152 | transition: opacity .3s ease-out; 153 | opacity: 0; 154 | } 155 | 156 | .history-view { 157 | width: 100%; 158 | height: 88px; 159 | margin-top: 36px; 160 | background-color: #292d44 161 | } 162 | 163 | .icon-delete { 164 | background-image: url('../../assets/image/delete.png'); 165 | height: 18px; 166 | } 167 | 168 | .color-view { 169 | width: 100%; 170 | margin-top: 6px; 171 | text-align: center 172 | } 173 | 174 | .color-view .color-list { 175 | display: inline-block; 176 | width: 28px; 177 | height: 28px; 178 | border-radius: 50%; 179 | margin: 0 6px; 180 | box-shadow: 8px 8px 16px rgba(0,0,0,.4); 181 | transition: transform .15s ease-out; 182 | transform: scale(1); 183 | cursor: pointer; 184 | } 185 | 186 | .color-list:hover { 187 | transform: scale(1.2) 188 | } 189 | 190 | .footer { 191 | line-height: 20px 192 | } 193 | 194 | .footer .main-text { 195 | color: #2b2b4c; 196 | font-size: 12px; 197 | } -------------------------------------------------------------------------------- /app/renderer/home/shortKey.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: A keyborad input listenter. 3 | * @Author: Bruce.Au 4 | * @LastEditors: Please set LastEditors 5 | * @Date: 2019-03-21 23:19:57 6 | * @LastEditTime: 2019-03-27 18:57:47 7 | */ 8 | 9 | const isMac = /^Mac/.test(navigator.platform) 10 | 11 | const commands = ['Command', 'Control', 'Shift','Capslock', 'Alt', 'Tap', 'Enter', 'Delete', 'Backspace'] 12 | 13 | const numberKeys = [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'] 14 | 15 | const symbolKeys = [ '-', '=', '[', ']', '\\', ';', "'", ',', '.', '.', '/'] 16 | 17 | const letterKeys = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] 18 | 19 | const _MATCH_DIGIT_ = /^(Digit)(.*)/ 20 | 21 | const _MATCH_kEY_ = /^(Key)(.*)/ 22 | 23 | const _MATCH_NUMBER_ = /^(Numpad)(.*)/ 24 | 25 | const _MATCH_RESOLVE_ = { 26 | MetaLeft : isMac ? 'Command' : 'Meta', 27 | MetaRight : isMac ? 'Command' : 'Meta', 28 | ControlLeft : 'Control', 29 | ControlRight : 'Control', 30 | AltLeft : 'Alt', 31 | AltRight : 'Alt', 32 | ShiftLeft : 'Shift', 33 | ShiftRight : 'Shift', 34 | Minus : '-', 35 | Equal : '=', 36 | Backslash : '\\', 37 | BracketLeft : '[', 38 | BracketRight : ']', 39 | Semicolon : ';', 40 | Quote : "'", 41 | Comma : ',', 42 | Period : '.', 43 | Slash : '/', 44 | Add : '+', 45 | Subtract : '-', 46 | Multiply : '*', 47 | Divide : '/', 48 | Decimal : 'Delete' 49 | } 50 | 51 | 52 | class ShortKeys{ 53 | 54 | constructor(options = {}) { 55 | this.commands = options.command || commands 56 | this.attendDemand = options.attend || ['number', 'symbol', 'letter'] 57 | this.ele = options.ele || document 58 | this.attends = [] 59 | this.commandKeys = {} 60 | this.currentKeys = [] 61 | this.attendKey = '' 62 | this.keyboardValue = '' 63 | this.focus = false 64 | this.onkeypressCallback = () => {} 65 | this.init() 66 | } 67 | 68 | init() { 69 | this.combination = this.Combination.bind(this) 70 | this.addEventKey() 71 | this.checkLocal() 72 | } 73 | 74 | checkLocal() { 75 | this.attendDemand.forEach(name => { 76 | if (name === 'number') { 77 | this.attends = [...this.attends, ...numberKeys] 78 | } 79 | if (name === 'symbol') { 80 | this.attends = [...this.attends, ...symbolKeys] 81 | } 82 | if (name === 'letter') { 83 | this.attends = [...this.attends, ...letterKeys] 84 | } 85 | }) 86 | } 87 | 88 | matchKey (key) { 89 | if (_MATCH_DIGIT_.test(key)) { 90 | key = key.replace(_MATCH_DIGIT_, '$2') 91 | } 92 | if (_MATCH_kEY_.test(key)) { 93 | key = key.replace(_MATCH_kEY_, '$2') 94 | } 95 | if (_MATCH_NUMBER_.test(key)) { 96 | key = key.replace(_MATCH_NUMBER_, '$2') 97 | } 98 | if (_MATCH_RESOLVE_[key]) { 99 | key = _MATCH_RESOLVE_[key] 100 | } 101 | return key.toLowerCase().replace(/^[a-z]/, $1=>$1.toUpperCase()); 102 | } 103 | 104 | Combination(event) { 105 | if (!this.focus) return 106 | event.preventDefault() 107 | event.returnValue=false 108 | const code = this.matchKey(event.code) 109 | const value = event.type === 'keydown' 110 | if (this.commands.includes(code)) { 111 | this.commandKeys[code] = value 112 | } 113 | if (this.attends.includes(code)) { 114 | this.attendKey = this.attendKey === code ? '' : code 115 | } 116 | value ? this.keydownEvent() : this.keyupEvent() 117 | } 118 | 119 | keydownEvent() { 120 | this.currentKeys = this.commands.filter(key => { 121 | if (this.commandKeys[key]) return true 122 | else return false 123 | }) 124 | if (this.currentKeys.length > 0) { 125 | this.currentKeys.push(this.attendKey) 126 | } 127 | this.keyboardValue = this.currentKeys.join('+') 128 | this.onkeypressCallback(this.keyboardValue, this) 129 | } 130 | 131 | keyupEvent() { 132 | if (this.currentKeys[this.currentKeys.length - 1] === '') { 133 | this.clearKeyboard() 134 | this.onkeypressCallback(this.keyboardValue, this) 135 | } 136 | } 137 | 138 | clearKeyboard() { 139 | this.currentKeys = [] 140 | this.commandKeys = {} 141 | this.attendKey = '' 142 | this.keyboardValue = '' 143 | } 144 | 145 | resetKeys(keys) { 146 | this.clearKeyboard() 147 | if (Array.isArray(keys)) { 148 | this.currentKeys = keys 149 | this.keyboardValue = this.currentKeys.join('+') 150 | this.onkeypressCallback(this.keyboardValue, this) 151 | } 152 | } 153 | 154 | addEventKey() { 155 | this.ele.addEventListener('keydown', this.combination) 156 | this.ele.addEventListener('keyup', this.combination) 157 | } 158 | onkeypress(fn) { 159 | if (typeof fn === 'function') { 160 | this.onkeypressCallback = fn 161 | } 162 | } 163 | 164 | destroy() { 165 | this.ele.removeEventListener('keydown', this.combination) 166 | this.ele.removeEventListener('keyup', this.combination) 167 | } 168 | 169 | onFocus() { 170 | this.focus = true 171 | } 172 | 173 | onBlur() { 174 | this.focus = false 175 | } 176 | 177 | } 178 | 179 | module.exports = ShortKeys -------------------------------------------------------------------------------- /app/main.js: -------------------------------------------------------------------------------- 1 | // Modules to control application life and create native browser window 2 | const { app, BrowserWindow, ipcMain, Tray, globalShortcut, Notification } = require('electron') 3 | const { resolve } = require('path') 4 | const { createMenu, menuBuild } = require('./menur') 5 | const { changeShort, pushColor, getColor } = require('./db') 6 | const { actions, DEFAULTE_KEYS, HISTORY_COLOR } = require('./store') 7 | const ASSETS_PATH = resolve(__dirname, 'assets') 8 | const APP_ICON = resolve(ASSETS_PATH, 'image/icon_app.png') 9 | const WHILTE_ICON = resolve(ASSETS_PATH, 'image/icon_tray.png') 10 | const MAIN_HTML = resolve(ASSETS_PATH, 'index.html') 11 | const PICK_HTML = resolve(ASSETS_PATH, 'pick.html') 12 | 13 | if (process.env.NODE_ENV === 'dev') { 14 | require('electron-debug')({ showDevTools: false }) 15 | } 16 | 17 | const isMac = process.platform === 'darwin' 18 | 19 | let mainWindow 20 | let pickWindow 21 | let trayApp 22 | let notification 23 | let shortcutCatch 24 | 25 | // 不同的action操作中间件 26 | const appliction = (action, event) => { 27 | switch (action.type) { 28 | case DEFAULTE_KEYS: 29 | changeShort(action.payload) 30 | let keys = action.payload.toLowerCase() 31 | setShortCut(keys) 32 | return action.payload 33 | 34 | case HISTORY_COLOR: 35 | pushColor(action.payload) 36 | return getColor() 37 | 38 | default: 39 | return action.payload 40 | } 41 | } 42 | 43 | // 注册全局快捷键 44 | function setShortCut(shortcut) { 45 | shortcut = shortcut && shortcut.toLowerCase() 46 | // 如果指令有问题,则不注册 47 | if (!shortcut || shortcut.indexOf('+') < 0) { 48 | return 49 | } 50 | // 注册之前删除上一次注册的全局快捷键 51 | if (shortcutCatch) { 52 | globalShortcut.unregister(shortcutCatch) 53 | } 54 | shortcutCatch = shortcut 55 | globalShortcut.register(shortcut, startByShort); 56 | } 57 | 58 | // 全局变量初始化 59 | function createStore (actions) { 60 | global.Store = {} 61 | actions.forEach(action => { 62 | global.Store[action.type] = action.default 63 | }) 64 | } 65 | 66 | // 连接到两个window 67 | function connect (appliction) { 68 | ipcMain.on('connct-store-context', (event, action) => { 69 | global.Store[action.type] = appliction(action) 70 | mainWindow && mainWindow.webContents.send('connct-store-provider', action) 71 | pickWindow && pickWindow.webContents.send('connct-store-provider', action) 72 | }) 73 | } 74 | 75 | // 创建tray 76 | function createtTray(icon) { 77 | let trayMenu = menuBuild(mainWindow, startByShort) 78 | trayApp = new Tray(icon) 79 | trayApp.setContextMenu(trayMenu) 80 | trayApp.on('right-click', () => { // 右键点击 81 | if (isMac) { 82 | mainWindow.center() 83 | mainWindow.show() 84 | } 85 | }) 86 | trayApp.on('click', () => { // 右键点击 87 | mainWindow.center() 88 | mainWindow.show() 89 | }) 90 | } 91 | 92 | // 快捷键对应的响应事件 93 | function startByShort() { 94 | mainWindow.webContents.send('shortcut-show'); 95 | } 96 | 97 | // windows下使用任务栏气泡通知 98 | 99 | function trayMessage(content) { 100 | if (isMac || !trayApp) return 101 | trayApp.displayBalloon({icon: APP_ICON, title: 'Color Point', content}) 102 | } 103 | 104 | // 绑定ipc消息 105 | function ipcMessager(main) { 106 | 107 | // 开始选择颜色 108 | ipcMain.on('start-point', function(event, arg) { 109 | const mainVisble = main.isVisible() 110 | main.setPosition(arg.width, arg.height) 111 | main.hide() 112 | pickWindow.setSize(arg.width, arg.height) 113 | pickWindow.show() 114 | pickWindow.webContents.send('start-point-pr', mainVisble) 115 | }) 116 | 117 | // 关闭颜色选择窗口并打开主窗口 118 | ipcMain.on('close-pick-window', function(e, type) { 119 | pickWindow.hide() 120 | if (type) { 121 | main.center() 122 | main.show() 123 | } 124 | }) 125 | 126 | // 复制颜色成功显示通知窗 127 | ipcMain.on('show-notification', function() { 128 | notification.show() 129 | trayMessage('颜色已复制') 130 | }) 131 | } 132 | 133 | 134 | async function createWindow () { 135 | // Create the browser window. 136 | notification = new Notification({ 137 | title: 'Color Point', 138 | body: '颜色已复制', 139 | silent: true 140 | }) 141 | 142 | mainWindow = new BrowserWindow({ 143 | width: 400, 144 | height: isMac ? 390 : 400, 145 | resizable: false, 146 | title: 'Color Point', 147 | backgroundColor: '#111327', 148 | icon: APP_ICON, 149 | darkTheme: true, 150 | fullscreenWindowTitle: true, 151 | show: false 152 | }) 153 | 154 | pickWindow = new BrowserWindow({ 155 | width: 10, 156 | height: 10, 157 | x: 0, 158 | y: 0, 159 | fullscreen: !isMac || undefined, 160 | resizable: false, 161 | movable: false, 162 | skipTaskbar: true, 163 | hasShadow: true, 164 | frame: false, 165 | alwaysOnTop: true, 166 | transparent: true, 167 | show: false 168 | }) 169 | 170 | ipcMessager(mainWindow) 171 | createMenu(mainWindow) 172 | createtTray(WHILTE_ICON) 173 | createStore(actions) 174 | connect(appliction) 175 | setShortCut(global.Store.DEFAULTE_KEYS) 176 | 177 | pickWindow.loadFile(PICK_HTML) 178 | mainWindow.loadFile(MAIN_HTML) 179 | 180 | mainWindow.once('ready-to-show', () => { 181 | mainWindow.show() 182 | }) 183 | 184 | mainWindow.on('show', function() { 185 | !isMac && mainWindow.setSkipTaskbar(false) 186 | }) 187 | 188 | mainWindow.on('close', function(event) { 189 | event.preventDefault(); 190 | mainWindow.hide(); 191 | !isMac && mainWindow.setSkipTaskbar(true); 192 | trayMessage('任务最小化到托盘') 193 | }) 194 | 195 | mainWindow.on('closed', function (e) { 196 | pickWindow.destroy() 197 | mainWindow = null 198 | }) 199 | 200 | pickWindow.on('closed', function() { 201 | pickWindow = null 202 | app.quit() 203 | }) 204 | } 205 | 206 | app.on('ready', createWindow) 207 | 208 | app.on('window-all-closed', function () { 209 | if (process.platform !== 'darwin') { 210 | app.quit() 211 | } 212 | }) 213 | 214 | app.on('activate', function () { 215 | if (mainWindow === null) { 216 | createWindow() 217 | } 218 | mainWindow.isVisible() || mainWindow.show() 219 | }) 220 | app.setName('Color Point') 221 | -------------------------------------------------------------------------------- /app/renderer/pickPage.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer, clipboard } = require( "electron" ); 2 | const { mutation } = require('./store') 3 | const screenshots = require('desktop-screenshot'); 4 | const { resolve } = require('path') 5 | const os = require('os') 6 | const fs = require('fs') 7 | const getRGB = (str) => { 8 | if (!str) return [,,,] 9 | const [r, g, b] = str.replace(/^(rgba\()(.*)(\))$/, '$2').split(',') 10 | return [r, g, b] 11 | } 12 | 13 | const colorFormat = color => { 14 | if (color >= 255) { 15 | return '+' 16 | } 17 | if (color < 0) { 18 | return '-' 19 | } 20 | return Math.round(color) 21 | } 22 | 23 | class App { 24 | constructor(opt) { 25 | this.size = { 26 | width: screen.width, 27 | height: screen.height 28 | } 29 | this.inputLabel = [{ 30 | value: 1, 31 | text: 'HEX' 32 | }, { 33 | value: 2, 34 | text: 'RGBA' 35 | }, { 36 | value: 3, 37 | text: '(rgba)透明度' 38 | }] 39 | this.src = resolve(os.tmpdir(), 'screenshot.png') 40 | this.startType = false 41 | this.imgData = [] 42 | this.clipData = [] 43 | this.radius = 80 44 | this.range = 11 45 | this.valueType = 1 46 | this.inMenu = false 47 | this.currentColor = '' 48 | this.clipRange = Math.ceil((2 * this.radius) / this.range) 49 | this.view = opt.el || document.body 50 | this._init() 51 | } 52 | _init() { 53 | this.createCanvas() 54 | this.createMenu() 55 | this.createProxy() 56 | this.addEventListener() 57 | } 58 | 59 | createProxy() { 60 | let self = this 61 | this.alpha = new Proxy({}, { 62 | set(proxy, key, value) { 63 | self.changeRanger(key, value) 64 | proxy[key] = value 65 | return true 66 | } 67 | }) 68 | } 69 | 70 | changeRanger(key, value) { 71 | if (key === 'opacity') { 72 | this.input.getElementsByTagName('span')[0].innerText = value 73 | this.input.getElementsByTagName('input')[0].value = value * 10 74 | } 75 | if (key === 'background') { 76 | this.input.getElementsByTagName('i')[0].style.background = value 77 | } 78 | } 79 | 80 | createCanvas() { 81 | this.image = new Image() 82 | this.background = document.createElement('canvas') 83 | this.clipView = document.createElement('div') 84 | this.colorValue = document.createElement('span') 85 | this.canvas = document.createElement('canvas') 86 | this.bg = this.background.getContext('2d') 87 | this.ctx = this.canvas.getContext('2d') 88 | 89 | 90 | this.clipView.className = 'clip-view' 91 | this.clipView.style.width = `${2 * this.radius}px` 92 | this.clipView.style.height = `${2 * this.radius}px` 93 | this.clipView.style.left = `-${this.radius}px` 94 | this.clipView.style.top = `-${this.radius}px` 95 | this.clipView.appendChild(this.canvas) 96 | this.clipView.appendChild(this.colorValue) 97 | } 98 | 99 | createMenu() { 100 | this.menu = document.createElement('div') 101 | this.button = document.createElement('a') 102 | this.button.className = 'menu-show' 103 | this.menu.className = 'menu-view' 104 | this.labels = this.createLable() 105 | this.input = this.createRanger() 106 | this.labels.forEach(label => { 107 | this.menu.appendChild(label) 108 | }) 109 | this.menu.appendChild(this.button) 110 | this.menu.appendChild(this.input) 111 | } 112 | 113 | createRanger() { 114 | let div = document.createElement('div') 115 | let input = document.createElement('input') 116 | let value = document.createElement('span') 117 | let background = document.createElement('i') 118 | div.className = 'range-box' 119 | input.type = 'range' 120 | input.value = 5 121 | input.min = 0 122 | input.max = 10 123 | div.appendChild(background) 124 | div.appendChild(input) 125 | div.appendChild(value) 126 | this.bindEvent(background, 'click', function() { 127 | if (this.valueType !== 3) return 128 | this.alpha.background = this.alpha.background === 'rgba(255,255,255,1)' ? 'rgba(0,0,0,1)' : 'rgba(255,255,255,1)' 129 | }) 130 | this.bindEvent(input, 'input', function(e) { 131 | if (this.valueType !== 3) return 132 | this.alpha.opacity = e.target.value / 10 133 | }) 134 | return div 135 | } 136 | 137 | createLable () { 138 | let labels = [] 139 | this.inputLabel.forEach(label => { 140 | let div = document.createElement('div') 141 | div.className = 'canvas-label' 142 | div.setAttribute('data-value', label.value) 143 | div.innerHTML = ` 144 | 145 | ${label.text} 146 | ` 147 | labels.push(div) 148 | }) 149 | return labels 150 | } 151 | 152 | start(type) { 153 | this.startType = type 154 | this.imgGeted = false 155 | this.background.width = this.size.width 156 | this.background.height = this.size.height 157 | this.canvas.width = this.canvas.height = 2 * this.radius 158 | this.getScreenImage() 159 | } 160 | 161 | getScreenImage() { 162 | screenshots(this.src, error => { 163 | if(error) 164 | console.log("Screenshot failed", error); 165 | else 166 | this.image.src = this.src + '?' + Math.random() 167 | }); 168 | } 169 | 170 | drawBackground() { 171 | this.bg.drawImage(this.image, 0, 0, this.size.width, this.size.height) 172 | this.imgData = this.bg.getImageData(0, 0, this.size.width, this.size.height) 173 | this.imgGeted = true 174 | this.view.appendChild(this.background) 175 | document.body.appendChild(this.menu) 176 | this.alpha.background = 'rgba(255,255,255,1)' 177 | this.alpha.opacity = 0.5 178 | } 179 | 180 | bindEvent(el, type, fn) { 181 | fn = fn.bind(this) 182 | el = typeof el === 'string' ? this[el] : el 183 | el.addEventListener(type, fn) 184 | } 185 | 186 | addEventListener() { 187 | this.bindEvent('image', 'load', this.drawBackground) 188 | this.bindEvent('background', 'mouseenter', this.showClip) 189 | this.bindEvent('menu', 'mouseenter', this.hideClip) 190 | this.bindEvent('button', 'click', this.menuToggle) 191 | this.bindEvent('canvas', 'mousedown', this.handleMousedown) 192 | this.labels.forEach(label => { 193 | this.bindEvent(label, 'click', this.changeType) 194 | }) 195 | this.bindEvent(document.body, 'mousemove', this.drawEvent) 196 | } 197 | 198 | hideClip() { 199 | if (this.clipView.parentNode === this.view) { 200 | this.view.removeChild(this.clipView) 201 | } 202 | this.inMenu = true 203 | } 204 | 205 | showClip(e) { 206 | this.inMenu = false 207 | this.setPosition(e) 208 | this.getClipData() 209 | this.drawPoint() 210 | this.view.appendChild(this.clipView) 211 | } 212 | 213 | changeType(e) { 214 | let target = e.target 215 | while (target !== document.body) { 216 | if (target.dataset.value) { 217 | this.valueType = +target.dataset.value 218 | this.changeClass() 219 | break 220 | } 221 | target = target.parentNode 222 | } 223 | } 224 | 225 | changeClass() { 226 | document.querySelectorAll('.checked-value').forEach(dom => { 227 | dom.className = '' 228 | }) 229 | this.menu.querySelector(`div[data-value="${this.valueType}"]`).firstElementChild.className = 'checked-value' 230 | if (this.valueType === 3) { 231 | this.input.classList.add('is-alpha') 232 | } else { 233 | this.input.classList.remove('is-alpha') 234 | } 235 | } 236 | 237 | menuToggle(e) { 238 | if (this.button.className === 'menu-show') { 239 | this.button.className = 'menu-hide' 240 | this.menu.classList.add('hide-view') 241 | } else { 242 | this.button.className = 'menu-show' 243 | this.menu.classList.remove('hide-view') 244 | } 245 | } 246 | 247 | drawEvent(e) { 248 | if (!this.imgGeted || this.inMenu) return 249 | this.setPosition(e) 250 | this.getClipData() 251 | this.drawPoint() 252 | } 253 | 254 | setPosition(e) { 255 | this.point = { 256 | x: e.clientX, 257 | y: e.clientY 258 | } 259 | this.current = { 260 | x: e.clientX - this.background.offsetLeft, 261 | y: e.clientY - this.background.offsetTop 262 | } 263 | } 264 | 265 | getClipData () { 266 | this.clipData = [] 267 | let [x, y] = [~~(this.current.x - this.radius / this.range), ~~(this.current.y - this.radius / this.range)] 268 | let data = this.imgData.data 269 | let index, r, g, b, a 270 | if (!data) return 271 | for (let i = 0; i < this.clipRange; i ++) { 272 | for (let j = 0; j < this.clipRange; j ++) { 273 | index = ~~((y + i) * this.imgData.width + x + j) 274 | r = data[index * 4 + 0] 275 | g = data[index * 4 + 1] 276 | b = data[index * 4 + 2] 277 | a = data[index * 4 + 3] / 255 278 | this.clipData.push(`rgba(${r},${g},${b},${a})`) 279 | } 280 | } 281 | } 282 | 283 | drawPoint () { 284 | let ctx = this.ctx 285 | let resize = this.radius - this.range * this.clipRange / 2 286 | let length = this.clipData.length 287 | let current = ~~(length / 2) 288 | let x, y, cp = {} 289 | this.clipView.style.transform = `translate(${this.point.x}px, ${this.point.y}px)` 290 | ctx.save() 291 | for (let i = 0 ; i < length; i ++) { 292 | y = ~~(i / this.clipRange) 293 | x = i - y * this.clipRange 294 | if (i === current) { 295 | cp = { 296 | x: x * this.range + resize, 297 | y: y * this.range + resize 298 | } 299 | } else { 300 | ctx.lineWidth = 0.4 301 | ctx.strokeStyle = 'rgba(255,255,255,0.4)' 302 | ctx.fillStyle = this.clipData[i] 303 | ctx.fillRect(x * this.range + resize, y * this.range + resize, this.range, this.range) 304 | ctx.strokeRect(x * this.range + resize, y * this.range + resize, this.range, this.range) 305 | ctx.restore() 306 | } 307 | } 308 | ctx.lineWidth = 2 309 | ctx.strokeStyle = '#fc04db' 310 | ctx.fillStyle = this.clipData[current] 311 | ctx.fillRect(cp.x, cp.y, this.range, this.range) 312 | ctx.strokeRect(cp.x, cp.y, this.range, this.range) 313 | ctx.restore() 314 | this.setValue(this.clipData[current]) 315 | } 316 | setValue(val) { 317 | let text = '' 318 | this.currentRGB = getRGB(val) 319 | if (this.valueType === 1) { 320 | text = '#' 321 | this.currentRGB.forEach(color => { 322 | let int = parseInt(color, 10).toString(16).toUpperCase() 323 | text += int.length === 1 ? `0${int}` : int 324 | }) 325 | } else if (this.valueType === 2) { 326 | text = val 327 | } else if (this.valueType === 3) { 328 | let [r, g, b] = getRGB(val) 329 | let [r1, g1, b1] = getRGB(this.alpha.background) 330 | let r2, g2, b2, a2 = this.alpha.opacity 331 | r2 = colorFormat((r - r1 * (1 - a2)) / a2) 332 | g2 = colorFormat((g - g1 * (1 - a2)) / a2) 333 | b2 = colorFormat((b - b1 * (1 - a2)) / a2) 334 | text = `rgba(${r2},${g2},${b2},${a2})` 335 | } 336 | this.currentColor = text 337 | this.colorValue.innerText = text 338 | } 339 | 340 | 341 | handleMousedown(e) { 342 | if (e.button == 2 && this.valueType == 3) { 343 | this.alpha.background = `rgba(${this.currentRGB[0]},${this.currentRGB[1]},${this.currentRGB[2]},1)` 344 | } 345 | if (e.button == 0) { 346 | this.handleSend() 347 | } 348 | } 349 | 350 | handleSend () { 351 | mutation({ 352 | type: 'HISTORY_COLOR', 353 | payload: this.currentColor 354 | }) 355 | clipboard.writeText(this.currentColor) 356 | this.exitProject() 357 | setTimeout(()=>{ 358 | ipcRenderer.send('close-pick-window', this.startType) 359 | ipcRenderer.send('show-notification') 360 | }, 30) 361 | } 362 | 363 | exitProject() { 364 | this.valueType = 1 365 | this.inMenu = false 366 | this.currentColor = '' 367 | this.changeClass() 368 | this.imgData = [] 369 | this.clipData = [] 370 | this.canvas.width = this.canvas.height = 0 371 | this.background.width = this.background.height = 0 372 | if (this.menu.parentNode === document.body) { 373 | document.body.removeChild(this.menu) 374 | } 375 | if (this.clipView.parentNode === this.view) { 376 | this.view.removeChild(this.clipView) 377 | } 378 | if (this.background.parentNode === this.view) { 379 | this.view.removeChild(this.background) 380 | } 381 | fs.unlinkSync(this.src) 382 | } 383 | } 384 | const app = new App({ 385 | el: document.querySelector('.view') 386 | }) 387 | 388 | ipcRenderer.on('start-point-pr', function(event, type) { 389 | app.start(type) 390 | }) 391 | --------------------------------------------------------------------------------