ํด๋ฆญ์ ํต๊ณผ์ํค๊ณ , ๊ธ์จ์์ ๋ฐฐ๊ฒฝ์ ๋ง์ถ๊ธฐ โ ์ค๋ฒ๋ ์ด ์์ ฏ์ ๋ ๊ฐ์ง ํธ๋ฆญ
์์ธ ์์ ฏ ํ์ฅ์ "์๋ฌด ์นํ์ด์ง ์์" ๋ ์์ด์ผ ํ๋ค. ๊ทธ๋ฐ๋ฐ ๋จ์ ํ์ด์ง ์์ ๋ญ ๋ฎ์ผ๋ฉด ๋ ๊ฐ์ง ๋ฌธ์ ๊ฐ ์๊ธด๋ค. (1) ์์ ฏ์ด ์๋ ํ์ด์ง์ ํด๋ฆญ์ ๊ฐ๋ก๋ง๊ณ , (2) ์์ ฏ ๊ธ์จ๊ฐ ๋ฐฐ๊ฒฝ์ ๋ฌปํ๋ค. ์ด ๋์ ์ด๋ป๊ฒ ํ์๋์ง ์ ๋ฆฌํ๋ค.
ํธ๋ฆญ 1: ํด๋ฆญ์ ์๋๋ก ํต๊ณผ์ํค๊ธฐ (pointer-events)
์์ ฏ์ ํ๋ฉด ์ ์ฒด๋ฅผ ๋ฎ๋ ์ค๋ฒ๋ ์ด ์์ ๊ทธ๋ฆฐ๋ค. ์ด๊ฑธ ๊ทธ๋ฅ ๋๋ฉด ์ค๋ฒ๋ ์ด๊ฐ
ํ์ด์ง์ ๋ชจ๋ ํด๋ฆญ์ ์ผํจ๋ค. ํต์ฌ์ CSS pointer-events๋ค.
#stock-widget-root {
position: fixed;
inset: 0;
z-index: 2147483646;
pointer-events: none; /* ๊ธฐ๋ณธ: ํด๋ฆญ์ด ์๋ ํ์ด์ง๋ก ํต๊ณผ */
}
#stock-widget-root .stock-widget {
pointer-events: none; /* ์์ ฏ๋ ํ์์ ํด๋ฆญ ์ ๋ฐ์ */
}
/* ํธ์ง ๋ชจ๋์ผ ๋๋ง ์์ ฏ์ด ํด๋ฆญยท๋๋๊ทธ๋ฅผ ๋ฐ๋๋ค */
#stock-widget-root[data-edit-mode="true"] .stock-widget {
pointer-events: auto;
cursor: grab;
}
pointer-events: none์ด๋ฉด ๊ทธ ์์๋ ๋ง์ฐ์ค ์ด๋ฒคํธ์ ํฌ๋ช
ํด์ง๋ค. ํด๋ฆญ์ด
์์ ฏ์ ๊ทธ๋ฅ ํต๊ณผํด ์๋ ํ์ด์ง๋ก ๊ฐ๋ค. ์์ธ๋ ๋์ผ๋ก ๋ณด๋, ํ์ด์ง๋ ํ์์ฒ๋ผ
์ธ ์ ์๋ค.
๊ทธ๋ผ ์์ ฏ์ ์ฎ๊ธฐ๊ณ ์ถ์ ๋? Shift๋ฅผ ๋๋ฅด๊ฑฐ๋ ๋๋๊ทธ ์ค์ผ ๋๋ง ํธ์ง ๋ชจ๋๋ก ์ ํํ๋ค. ์ํ๋ฅผ ๋ฐ์ดํฐ ์์ฑ ํ๋๋ก ๊ด๋ฆฌํ๋ค.
function syncRootState() {
root.dataset.editMode =
shiftPressed || cmdPressed || selectionState || dragState || resizeState
? "true"
: "false";
}
data-edit-mode๊ฐ true๊ฐ ๋๋ ์๊ฐ ์์ ฏ์ด pointer-events: auto๋ก ๋ฐ๋์ด
๋๋๊ทธยท๋ฆฌ์ฌ์ด์ฆ๋ฅผ ๋ฐ๊ณ , ๋์ผ๋ฉด ๋ค์ ํฌ๋ช
ํด์ง๋ค. ์ํ๋ JS๋ก, ์ค์ ๋ฐ์์
CSS๋ก ๋๋ ๊ฒ ๊น๋ํ๋ค.
์ฌ๊ธฐ์ ์จ์ ํจ์ ์ด ๋ง์๋ค. ์๋ฅผ ๋ค์ด ์์ ฏ ์์ ์ ๋ ฅ์ฐฝ์ ํฌ์ปค์คํ๋ฉด ์๋ ํ์ด์ง(๋ค์ด๋ฒ ๋ฑ)์ ์๋์์ฑ๊ณผ ์ถฉ๋ํด์, capture ๋จ๊ณ์์
stopImmediatePropagation()์ผ๋ก ๋จผ์ ๊ฐ๋ก์ฑ์ผ ํ๋ค. "๋จ์ ์ง์ ์ธ๋ค์ด ์ฌ๋" ์ค๋ฒ๋ ์ด๋ผ ์ด๋ฐ ๊ฒฝ๊ณ ์ฒ๋ฆฌ๊ฐ ๊ณ์ ํ์ํ๋ค.
ํธ๋ฆญ 2: ๊ธ์จ์์ ๋ฐฐ๊ฒฝ์ ๋ง์ถ๊ธฐ (๋ช ์๋น ๊ณ์ฐ)
์์ ฏ์ ๋ฐํฌ๋ช ์ด๋ผ ๋ค ๋ฐฐ๊ฒฝ์ด ๋น์น๋ค. ํฐ ๋ฐฐ๊ฒฝ์ด๋ฉด ๊ฒ์ ๊ธ์จ, ์ด๋์ด ๋ฐฐ๊ฒฝ์ด๋ฉด ํฐ ๊ธ์จ์ฌ์ผ ์ฝํ๋ค. ๊ทธ๋์ ์์ ฏ ๋ค์ ์ค์ ๋ฐฐ๊ฒฝ์์ ์ํ๋งํ๊ณ , ๊ทธ์ ๋ง๋ ๊ธ์จ์์ ๊ณจ๋๋ค.
๋ฐฐ๊ฒฝ์์ ์์๋ด๋ ๊ฑด elementsFromPoint๋ก ๊ทธ ์ง์ ์ ์์ธ ์์๋ค์ ๋ฐฐ๊ฒฝ์์
ํ๋๋ค. (์ด๊ฒ ์ฑ๋ฅ ๋ณ๋ชฉ์ด ๋ผ์ ๋ฐ๋ก ํฌ๊ฒ ์ต์ ํํ ์ด์ผ๊ธฐ๋
์ฌ๊ธฐ์ ์๋ค.)
๊ทธ๋ค์์ด ํต์ฌ์ธ๋ฐ, "๊ฒ์ ์ด ๋์๊น ํฐ์์ด ๋์๊น"๋ฅผ ๊ฐ์ด ์๋๋ผ WCAG์ ๋ช ์๋น ๊ณต์์ผ๋ก ๊ณ์ฐํ๋ค.
// ์๋ ํ๋ (WCAG ์ ์)
function getRelativeLuminance(color) {
const { r, g, b } = parseColor(color);
const toLinear = (c) => {
const v = c / 255;
return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
};
return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
}
// ๋ ์์ ๋ช
์๋น (1:1 ~ 21:1)
function getContrastRatio(fg, bg) {
const L1 = getRelativeLuminance(fg);
const L2 = getRelativeLuminance(bg);
const [lighter, darker] = [Math.max(L1, L2), Math.min(L1, L2)];
return (lighter + 0.05) / (darker + 0.05);
}
๋ฐฐ๊ฒฝ์์ด ์ ํด์ง๋ฉด ๊ธ์จ์์ ์ด๋ ๊ฒ ๊ณ ๋ฅธ๋ค. ์ฐ์ ์์๊ฐ ์๋ค.
- ํ์ด์ง๊ฐ ์๋ ์ฐ๋ ํ ์คํธ์์ ์ต๋ํ ์กด์คํ๋ค. ์ํ ์ง์ ์ ํ ์คํธ์, ์ฃผ๋ณ ์ปจํ ์คํธ ์์์ ์, body ์ ์์ผ๋ก ํ๋ณด๋ฅผ ํ์ผ๋ฉด์ ๋ฐฐ๊ฒฝ๊ณผ์ ๋ช ์๋น๊ฐ 3.5 ์ด์์ธ ์ฒซ ํ๋ณด๋ฅผ "์ ํธ ์"์ผ๋ก ์ก๋๋ค. ์์ ฏ์ด ํ์ด์ง์ ์์ฐ์ค๋ฝ๊ฒ ๋ น์๋ค๊ฒ ํ๋ ค๋ ์๋๋ค.
- ๊ทธ ์ ํธ ์์ด ์ต์ข ๊ฒ์ฌ(๋ช ์๋น 4.5, WCAG AA ๊ธฐ์ค)๋ฅผ ํต๊ณผํ๋ฉด ๊ทธ๋๋ก ์ด๋ค.
- ํต๊ณผ ๋ชป ํ๋ฉด ๊ทธ๋์์ผ ๋ฐ์์ยท์ด๋์ด์ ๋ ํ๋ณด ์ค ๋ฐฐ๊ฒฝ๊ณผ์ ๋ช ์๋น๊ฐ ๋ ๋์ ์ชฝ์ผ๋ก ํด๋ฐฑํ๋ค.
function pickReadableTextColor(preferredColor, backgroundColor) {
const light = "rgb(248, 250, 252)";
const dark = "rgb(15, 23, 42)";
if (getContrastRatio(preferredColor, backgroundColor) >= 4.5) return preferredColor;
return getContrastRatio(light, backgroundColor) > getContrastRatio(dark, backgroundColor)
? light
: dark;
}
์ฌ๋์ด "์ด ์ ๋๋ฉด ์ฝํ๊ฒ ์ง" ํ๊ณ ์ฐ๋ ๋์ , ์ ๊ทผ์ฑ ํ์ค์ด ์ ์ํ ์์์ผ๋ก ์ ํ๋ ์ด๋ค ์ฌ์ดํธ ์์ ์ฌ๋ ค๋ ์ผ๊ด๋๊ฒ ์ฝํ๋ค.
๋ฐฐ์ด ๊ฒ
-
pointer-events๋ ์ค๋ฒ๋ ์ด UI์ ํต์ฌ ๋๊ตฌ๋ค. ์ํ๋ JS, ๋ฐ์์ CSS๋ก ๋๋๋ฉด ๊น๋ํ๋ค. -
"๊ธ์จ๊ฐ ์ ๋ณด์ธ๋ค"๋ ์ฃผ๊ด ๊ฐ์ง๋ง, WCAG ์๋ ํ๋ยท๋ช ์๋น๋ผ๋ ์ ๋ ๊ธฐ์ค์ด ์๋ค. ๊ฐ์ผ๋ก ์์ ๊ณ ๋ฅด์ง ์์๋ ๋๋ค.
-
๋จ์ ํ์ด์ง ์์ ์น๋ ํ์ฅ์ ๊ฒฐ๊ตญ "๋จ์ ํ๊ฒฝ์ ์นจ๋ฒํ์ง ์๊ธฐ"๊ฐ ์ ๋ฐ์ด๋ค. ํด๋ฆญ ํต๊ณผ, ์ด๋ฒคํธ ์ถฉ๋, ๋ฐฐ๊ฒฝ ์ ์ ์ ๋ถ ๊ทธ ์ฐ์ฅ์ ์ด์๋ค.
-
๊ด๋ จ ํ๋ก์ ํธ: Stock Price Widget