Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature / Add Magnifier To Eye Dropper #39

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react'
import { hot } from 'react-hot-loader'

import './App.css'
import { EyeDropper, OnChangeEyedrop, useEyeDrop } from './package'
import { EyeDropper, OnChangeEyedrop, useEyeDrop } from './dist'
import { ChangeEvent, useEffect } from 'react'
const { useState } = React

Expand Down Expand Up @@ -64,6 +64,7 @@ const App = () => {
{image ? (
<div className="eyedrop-wrapper">
<EyeDropper once={eyedropOnce} onChange={handleChangeColor}>Pick Color</EyeDropper>
<EyeDropper once={eyedropOnce} onChange={handleChangeColor} isMagnifiedPicker={true}>Magnified EyeDropper</EyeDropper>
<button onClick={pickColor}>Pick Color With Hook</button>
<p>Once: {eyedropOnce.toString()}</p>
<button onClick={toggleOnce}>Toggle `once` prop</button>
Expand Down
8 changes: 8 additions & 0 deletions src/colorUtils/hexToRgb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const hexToRgb = (hex: string) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
27 changes: 10 additions & 17 deletions src/colorUtils/rgbToHex.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import { RgbObj } from '../types';
import { RgbObj } from '../types'

const numberToHex = (rgb: number) => {
let hex = rgb.toString(16);
if (hex.length < 2) {
hex = `0${hex}`;
}
return hex;
};
const hex = rgb.toString(16)
return hex.length < 2 ? `0${hex}` : hex
}

export const rgbToHex = (rgbObj: RgbObj): string => {
const {
r,
g,
b
} = rgbObj;
const red = numberToHex(r);
const green = numberToHex(g);
const blue = numberToHex(b);
return `#${red}${green}${blue}`;
};
const { r, g, b } = rgbObj
const red = numberToHex(r)
const green = numberToHex(g)
const blue = numberToHex(b)
return `#${red}${green}${blue}`
}
7 changes: 7 additions & 0 deletions src/constants/Constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const DEFAULT_MAGNIFIER_SIZE = 150;
export const DEFAULT_PIXELATE_VALUE = 6;
export const DEFAULT_ZOOM_AMOUNT = 5;
export const PIXEL_BOX_MULTIPLIER = 2;
export const PIXEL_BOX_OFFSET = 3;
export const PIXELATE_THRESHOLD = 20;
export const ZOOM_THRESHOLD = 10;
140 changes: 93 additions & 47 deletions src/eyeDropper.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import * as React from 'react';
import { useRef } from 'react';

import { parseRGB } from './colorUtils/parseRgb';
import { rgbToHex } from './colorUtils/rgbToHex';
import { OnChangeEyedrop, RgbObj, PickingMode } from './types';
import { OnChangeEyedrop, RgbObj, PickingMode, TargetRef } from './types';
import { validatePickRadius } from './validations/validatePickRadius';
import { targetToCanvas } from './targetToCanvas'
import { getColor } from './getColor'
import { targetToCanvas } from './targetToCanvas';
import { getColor } from './getColor';
import { Magnifier } from './magnifier';

const {
useCallback,
Expand Down Expand Up @@ -40,15 +43,24 @@ type Props = {
pickRadius?: number,
disabled?: boolean,
children?: React.ReactNode,
customProps?: { [key: string]: any }
customProps?: { [key: string]: any },
isMagnifiedPicker?: boolean
zoom?: number,
pixelateValue?: number,
magnifierSize?: number,
areaSelector?: string,
}

const initialStateColors = { rgb: '', hex: '' };

export const EyeDropper = (props: Props) => {
const [colors, setColors] = useState(initialStateColors);
const [pickingColorFromDocument, setPickingColorFromDocument] = useState(false);
const [buttonDisabled, setButtonDisabled] = useState(false);
const [colors, setColors] = useState(initialStateColors)
const [pickingColorFromDocument, setPickingColorFromDocument] = useState(false)
const [buttonDisabled, setButtonDisabled] = useState(false)
const [active, setActive] = useState(false)
const [canvas, setCanvas] = useState<HTMLCanvasElement | null>(null)
const eyeDropperRef = useRef(document.createElement('div'))
const target = useRef<TargetRef>({} as TargetRef)
const {
once = true,
pickRadius = 0,
Expand All @@ -65,95 +77,118 @@ export const EyeDropper = (props: Props) => {
disabled,
onPickStart,
onPickEnd,
} = props;
isMagnifiedPicker = false,
pixelateValue = 6,
magnifierSize = 150,
zoom = 5,
areaSelector = 'body',
} = props

const setPickingMode = useCallback(({ isPicking, disableButton, showActiveCursor }: PickingMode) => {
if(document.body) {
document.body.style.cursor = showActiveCursor ? cursorActive : cursorInactive;
document.body.style.cursor = showActiveCursor ? cursorActive : cursorInactive
}
setPickingColorFromDocument(isPicking);
setButtonDisabled(disableButton);
}, [cursorActive, cursorInactive]);
setPickingColorFromDocument(isPicking)
setButtonDisabled(disableButton)
}, [cursorActive, cursorInactive])

const deactivateColorPicking = useCallback(
() => {
setPickingMode({
isPicking: false,
disableButton: false,
showActiveCursor: false
})
});
onPickEnd && onPickEnd()
}, [setPickingMode, onPickEnd]
);
)

const exitPickByEscKey = useCallback((event: KeyboardEvent) => {
event.code === 'Escape' && pickingColorFromDocument && deactivateColorPicking()
}, [pickingColorFromDocument, deactivateColorPicking]);
}, [pickingColorFromDocument, deactivateColorPicking])

const pickColor = () => {
if (onPickStart) { onPickStart(); }
if (onPickStart) { onPickStart() }

setPickingMode({
isPicking: true,
disableButton: disabled || true,
showActiveCursor: true
});
};
})
}

const updateColors = useCallback((rgbObj: RgbObj) => {
const rgb = parseRGB(rgbObj);
const hex = rgbToHex(rgbObj);
const activateMagnifier = () => {
setActive(!active)
}

// set color object to parent handler
onChange({ rgb, hex, customProps });
const updateColors = useCallback((rgbObj: RgbObj) => {
const rgb = parseRGB(rgbObj)
const hex = rgbToHex(rgbObj)

setColors({ rgb, hex });
}, [customProps, onChange]);
onChange({ rgb, hex, customProps })
setColors({ rgb, hex })
setActive(false)
}, [customProps, onChange])

const extractColor = useCallback(async (e: MouseEvent) => {
const { target } = e;
const { target } = e

const targetCanvas = await targetToCanvas(target)
const rgbColor = getColor(pickRadius, targetCanvas, e)
const targetCanvas = target && await targetToCanvas(target)
const rgbColor = targetCanvas && getColor(pickRadius, targetCanvas, e)

updateColors(rgbColor)
once && deactivateColorPicking();
}, [deactivateColorPicking, once, pickRadius, updateColors]);
rgbColor && updateColors(rgbColor)
once && deactivateColorPicking()
}, [deactivateColorPicking, once, pickRadius, updateColors])

useEffect(() => {
onInit && onInit();
}, [onInit]);
onInit && onInit()
}, [onInit])

useEffect(() => {
pickRadius && validatePickRadius(pickRadius);
}, [pickRadius]);
pickRadius && validatePickRadius(pickRadius)
}, [pickRadius])

// setup listener for canvas picking click
useEffect(() => {
if (pickingColorFromDocument) {
document.addEventListener('click', extractColor);
document.addEventListener('click', extractColor)
}
return () => {
document.removeEventListener('click', extractColor);
};
}, [pickingColorFromDocument, once, extractColor]);
document.removeEventListener('click', extractColor)
}
}, [pickingColorFromDocument, once, extractColor])

// setup listener for the esc key
useEffect(() => {
if (pickingColorFromDocument) {
document.addEventListener('keydown', exitPickByEscKey);
document.addEventListener('keydown', exitPickByEscKey)
}
return () => {
document.removeEventListener('keydown', exitPickByEscKey);
};
}, [pickingColorFromDocument, exitPickByEscKey]);
document.removeEventListener('keydown', exitPickByEscKey)
}
}, [pickingColorFromDocument, exitPickByEscKey])

useEffect(() => {
if (active) {
const targetEle = eyeDropperRef.current.ownerDocument.querySelector(
areaSelector
) as HTMLElement
if (targetEle) {
target.current = {
element: targetEle,
rect: targetEle.getBoundingClientRect(),
}
targetToCanvas(targetEle).then(setCanvas)
}
}
}, [active])

const shouldColorsPassThrough = colorsPassThrough ? { [colorsPassThrough]: colors } : {};
const shouldColorsPassThrough = colorsPassThrough ? { [colorsPassThrough]: colors } : {}
return (
<div style={styles.eyedropperWrapper} className={wrapperClasses}>
<div style={styles.eyedropperWrapper} className={wrapperClasses} ref={eyeDropperRef}>
{CustomComponent ? (
<CustomComponent
onClick={pickColor}
onClick={isMagnifiedPicker ? activateMagnifier : pickColor}
{...shouldColorsPassThrough}
customProps={customProps}
disabled={buttonDisabled}
Expand All @@ -172,13 +207,24 @@ export const EyeDropper = (props: Props) => {
<button
id={'react-eyedrop-button'}
className={`react-eyedrop-button ${buttonClasses || ''}`}
onClick={pickColor}
onClick={isMagnifiedPicker ? activateMagnifier : pickColor}
disabled={buttonDisabled}
>
{children}
</button>
</>
)}
{isMagnifiedPicker && (
<Magnifier
active={active}
canvas={canvas}
zoom={zoom}
pixelateValue={pixelateValue}
magnifierSize={magnifierSize}
setColorCallback={updateColors}
target={target}
/>
)}
</div>
);
};
3 changes: 2 additions & 1 deletion src/getColor/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as getCanvasPixelColor from 'get-canvas-pixel-color'
import { extractColors } from './extractColors'
import { calcAverageColor } from './calcAverageColor'
import { RgbObj } from '../types'

export const getColor = (pickRadius: number, targetCanvas: HTMLCanvasElement, e: MouseEvent) => {
export const getColor = (pickRadius: number, targetCanvas: HTMLCanvasElement, e: MouseEvent): RgbObj => {
const { offsetX, offsetY } = e;
if (pickRadius === undefined || pickRadius === 0) {
return getCanvasPixelColor(targetCanvas, offsetX, offsetY);
Expand Down
Loading