[JS] ์๋ฐ์คํฌ๋ฆฝํธ 6์ค๋ก ์ด๋ฏธ์ง ๋น๊ต ์ฌ๋ผ์ด๋ ๋ง๋ค๊ธฐ
์ด๋ฏธ์ง ๋น๊ต ์ฌ๋ผ์ด๋๋ ํธ๋ค์ ์ข์ฐ๋ก ์์ง์ฌ์ ๋ ์ด๋ฏธ์ง๋ฅผ ๋น๊ตํ ์ ์๋ UI ํจํด์ผ๋ก ์ผํ๋ชฐ์ด๋ ์ ํ ์๊ฐ ํ์ด์ง ๋ฑ์์ ์์ฃผ ๋ณผ ์ ์๋ค. ์ด๋ฌํ ์ด๋ฏธ์ง ๋น๊ต ์ฌ๋ผ์ด๋๋ range
ํ์
์ input
ํ๊ทธ์ 6์ค ์ ๋์ ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋๋ง์ผ๋ก ๊ตฌํํ ์ ์๋ค.
range
ํ์
์ input
์ ์ฌ๋ผ์ด๋๋ฐ๋ฅผ ์กฐ์ ํ์ฌ ๋ฒ์ ๋ด ์ซ์ ๊ฐ์ ์ ํํ ์ ์๋ ์
๋ ฅ ํ๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก ๋ธ๋ผ์ฐ์ ์์ ์ ๊ณตํ๋ ์คํ์ผ์ด ์ ์ฉ๋์ด ์๋ค. ์ด ๊ธฐ๋ณธ ์คํ์ผ์ ๋ชจ๋ ์ ๊ฑฐํ๊ณ ์ด๋ฏธ์ง์ ๋๋น/๋์ด๋งํผ ํฌ๊ธฐ๋ฅผ ํ์ฅํ๋ฉด, ์ฌ๋ผ์ด๋ ์์ญ์ ํด๋ฆญ ํน์ ๋๋๊ทธํ ๋๋ง๋ค (์คํ์ผ์ ์ ๊ฑฐํด์ ํ๋ฉด์ ๋ณด์ด์ง ์๋) ํธ๋ค์ด ํด๋น ์์น๋ก ์ด๋ํด์ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ค.
๊ธฐ๋ณธ์ ์ธ ๊ตฌํ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ๋ค.
์ด๋ฏธ์ง 1, ์ด๋ฏธ์ง 2๋ฅผ ๊ฐ์ธ๋ ๋ง์คํฌ ์์, range input, ๊ตฌ๋ถ์ (Separator) ๋ฐ ์ปค์คํ ํธ๋ค ์ด๋ ๊ฒ 4๊ฐ ๋ ์ด์ด๋ฅผ ํฌ๊ฐ์ง๋๋ก ๊ตฌ์ฑํ๋ค. ํธ๋ค์ ์์ง์ผ ๋๋ง๋ค ์ฌ๋ผ์ด๋(range input) ๊ฐ ๋งํผ ๋ง์คํฌ ์์์ ๋๋น๋ฅผ ์กฐ์ ํ๊ณ , ๋์น๋ ๋ถ๋ถ์ ๋ณด์ด์ง ์๋๋ก ์ฒ๋ฆฌํ๋ฉด(overflow: hidden) ๊ธฐ๋ณธ์ ์ธ ์ด๋ฏธ์ง ๋น๊ต ์ฌ๋ผ์ด๋๊ฐ ์์ฑ๋๋ค.
HTML ๊ตฌ์กฐ
์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง๋ .compare-img-one
ํด๋์ค๋ก ์ง์ ๋ฐฐ์นํ๊ณ (๊ฐ์ฅ ํ๋จ), ๋ ๋ฒ์งธ ์ด๋ฏธ์ง๋ ๋ง์คํน ์ฒ๋ฆฌ๋ฅผ ์ํด .compare-mask
์์ ์์ ํฌํจ๋๋๋ก ํ๋ค. ๋ ๋ฒ์งธ ์ด๋ฏธ์ง๋ ์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง ์์ ๊ฒน์น ์ํ๋ก ์์นํ๊ฒ ๋๋ค. ๊ตฌ๋ถ์ ๊ณผ ์ปค์คํ
ํธ๋ค์ .compare-mask
์ฐ์ธก ๋์ ์์นํด์ ์ด๋ฏธ์ง 1๊ณผ์ ๊ฒฝ๊ณ๋ฅผ ๋ํ๋ธ๋ค.
.compare-input
์์๋ ํด๋ฆญ ํน์ ๋๋๊ทธ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด ๊ฐ์ฅ ์๋จ์ ์์นํ๊ฒ ๋๋ค. ์ฐธ๊ณ ๋ก HTML ๋ฌธ์์์ ํ๋จ์ ์์ฑํ ์์์ผ ์๋ก ํ๋ฉด์์ ์์ ํ์๋๋ค.
<section class="compare">
<!-- ์ด๋ฏธ์ง 1 -->
<img class="compare-img-one" src="..." />
<!-- ์ด๋ฏธ์ง 2 ๋ง์คํฌ -->
<div class="compare-mask">
<!-- ์ด๋ฏธ์ง 2 -->
<img class="compare-img-two" src="..." />
</div>
<!-- ์ด๋ฏธ์ง 1, 2 ๊ตฌ๋ถ์ -->
<div class="compare-separator">
<!-- ์ปค์คํ
ํธ๋ค (svg ์์ฑ ๋ฐ path ์๋ต) -->
<svg class="compare-icon"><!-- ... --></svg>
</div>
<!-- range input -->
<input class="compare-input" type="range" min="0" step="0.5" max="100" />
</section>
CSS
์ปจํ ์ด๋
์์ ์์๋ค์ ์ปจํ
์ด๋์ธ .compare
์์ ๊ธฐ์ค์ผ๋ก ๋ฐฐ์นํ๊ธฐ ์ํด position: relative
์์ฑ์ ์ค์ ํ๋ค. ํ์ํ CSS ๋ณ์๋ ์ปจํ
์ด๋์ ์ ์ํ๋ค. ๋ถ๋ชจ ์์์์ ์ ์ํ CSS ๋ณ์๋ ์์ ์์์์ ์ฐธ์กฐํ ์ ์๋ค.
.compare {
--mask-width: 50%; /* slider value */
--handle-size: 33px;
position: relative;
}
์ด๋ฏธ์ง 1
Baseline (๋ฒ ์ด์ค๋ผ์ธ): ๊ธ์ ์๋์ ์๋ ๊ฐ์์ ์ . ํ ์คํธ ์์๋ ๊ธ์๋ง๋ค ๋์ด๊ฐ ๋ค๋ฅด๋๋ผ๋ ์ด ์ ์ ๊ธฐ์ค์ผ๋ก ๋๋ํ ๋ฐฐ์น๋๋ค. g, j, p, q, y ๊ฐ์ ๊ธ์๋ ๊ผฌ๋ฆฌ ๋ถ๋ถ์ด ๋ฒ ์ด์ค๋ผ์ธ ์๋๋ก ๋ด๋ ค๊ฐ๋ค. ์ด๋ ๊ฒ ๋ฒ ์ด์ค๋ผ์ธ ํ๋จ์ผ๋ก ํ์ฅ๋๋ ๋ถ๋ถ์ Descender๋ผ๊ณ ๋ถ๋ฅธ๋ค.
์ด๋ฏธ์ง ํ๊ทธ์ display
์์ฑ ๊ธฐ๋ณธ๊ฐ์ inline
์ด๊ธฐ ๋๋ฌธ์ ํ
์คํธ์ฒ๋ผ ๋ฒ ์ด์ค๋ผ์ธ ๊ธฐ์ค์ผ๋ก ์ ๋ ฌ๋๋ค. ์ด๋ ์ด๋ฏธ์ง ํ๋จ์ descender๋ฅผ ๊ฐ์ง ๊ธ์๋ค์ ์ํ ๊ณต๊ฐ์ด ์๋์ผ๋ก ์์ฑ๋๋ค. ์ด ํ๋จ ์ฌ๋ฐฑ์ ์์ ๊ณ ์ถ์ผ๋ฉด ์ด๋ฏธ์ง์ display
์์ฑ์ block
์ผ๋ก ์ค์ ํ๋ฉด ๋๋ค.
.compare-img-one {
width: 100%; /* ๋ถ๋ชจ ์์ ๋๋น๋งํผ ์ด๋ฏธ์ง ํฌ๊ธฐ ์ ํ */
display: block; /* ์ด๋ฏธ์ง ์๋ ์์ฑ๋๋ ํ๋จ ์ฌ๋ฐฑ์ ์์ ๊ธฐ ์ํด block์ผ๋ก ๋ณ๊ฒฝ */
}
์ด๋ฏธ์ง 2 ๋ง์คํฌ
์ด๋ฏธ์ง ๋ง์คํฌ๋ ์ปจํ
์ด๋๋ฅผ ๊ธฐ์ค์ผ๋ก ์์นํด์ผ ํ๋ฏ๋ก position: absolute
๋ฅผ ์ค์ ํ๋ค. ์ฌ๋ผ์ด๋ ํธ๋ค์ ์กฐ์ ํ ๋๋ง๋ค ์ฌ๋ผ์ด๋ ๊ฐ์ด --mask-width
CSS ๋ณ์์ ๋ฐ์๋๋ฉฐ, ์ด ๊ฐ์ ์ด๋ฏธ์ง ๋ง์คํฌ ์์์ ๋๋น๋ก ์ ์ฉ๋๋ค. ๋ง์ฝ --mask-width
๊ฐ 50%๋ผ๋ฉด ์ด๋ฏธ์ง 2๋ ์ ๋ฐ๋ง ํ์๋๋ค. ๋๋จธ์ง ์์ญ์ overflow: hidden
์์ฑ์ ์ํด ์๋ฆผ์ฒ๋ฆฌ ๋๊ณ , ๊ทธ ์๋์ ์๋ ์ด๋ฏธ์ง 1์ด ํ์๋๋ค.
.compare-mask {
position: absolute;
inset: 0;
width: var(--mask-width);
height: 100%;
overflow: hidden;
}
.compare-img-two {
height: 100%;
/* width: auto ๊ธฐ๋ณธ๊ฐ -> ์๋ณธ ์ด๋ฏธ์ง ๋๋น๋ก ๋ ๋๋ง */
}
์ด๋ฏธ์ง ๊ตฌ๋ถ์ , ์ปค์คํ ํธ๋ค
ํ์ฌ ์ค์ ๋ ์ด๋ฏธ์ง ๋ง์คํฌ ๋๋น๋งํผ ๊ตฌ๋ถ์ ์ ์ค๋ฅธ์ชฝ์ผ๋ก ์ด๋์ํค๋ฉด ์ด๋ฏธ์ง ๋ง์คํฌ ๋ฐ๋ก ์ฐ์ธก(ํ๋ฉด์ ๋ณด์ด๋ ์ด๋ฏธ์ง 1 ์์ ์ง์ )์ ์์นํ๊ฒ ๋๋ค. ์ด ์ํ์์ ๊ตฌ๋ถ์ ๋๋น์ ์ ๋ฐ๋งํผ ๋ค์ ์ผ์ชฝ์ผ๋ก ์ด๋ํ๋ฉด ์ ํํ ๋ ์ด๋ฏธ์ง์ ๊ฒฝ๊ณ์ ๊ฑธ์น๊ฒ ๋๋ค.
.compare-separator {
position: absolute;
inset: 0;
width: 2px;
height: 100%;
left: var(--mask-width); /* ๋ง์คํฌ ๋๋น๋งํผ ์ค๋ฅธ์ชฝ์ผ๋ก ์ด๋ */
translate: -50%; /* ์์ ๋๋น์ 50% ๋งํผ ์ผ์ชฝ์ผ๋ก ์ด๋(1px) */
background: var(--bg-color);
}
์ปค์คํ
ํธ๋ค ์ญ์ top
, left
, translate
์์ฑ์ ์ด์ฉํด์ ๊ฒฝ๊ณ์ ์ค์์ ๊ฑธ์น๋๋ก ์์ฑํ๋ค. ์ฐธ๊ณ ๋ก ๋๋ถ๋ถ์ ๋ธ๋ผ์ฐ์ ์์ translate
, rotate
, scale
๋ฑ์ ์์ฑ์ ๋
๋ฆฝ์ ์ธ CSS ์์ฑ์ผ๋ก ์ฌ์ฉํ ์ ์๋ค. 2021๋
์ด์ ์ ๊ตฌํ ๋ธ๋ผ์ฐ์ ์์ ์ฌ์ ํ transform: translate(x, y)
ํํ๋ก ์ฌ์ฉํด์ผ ํ๋ค.
.compare-icon {
position: absolute;
top: 50%;
left: var(--mask-width);
width: var(--handle-size);
height: var(--handle-size);
translate: -50% -50%;
padding: 6px;
color: var(--bg-color);
background: white;
stroke-width: 2px;
border: 2px solid currentColor;
border-radius: 50%;
}
Range Input
์ปค์คํ ํธ๋ค ์ฝ๋๋ฅผ ์ ์ ์ ๊ฑฐํ ๋ค ๊ฐ์ฅ์๋ฆฌ๋ก ๋๋๊ทธํด ๋ณด๋ฉด, ๊ตฌ๋ถ์ ๊ณผ ํธ๋ค ์ฌ์ด์ ๊ฐ๊ฒฉ์ด ์๊ธฐ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค. ์ด ๊ฐ๊ฒฉ์ ์ ํํ ํธ๋ค ๋๋น์ ์ ๋ฐ๋งํผ ๋ฒ์ด์ง๋ค. ํธ๋ค์ด ์ฌ๋ผ์ด๋ ์์ญ์ ๋ฒ์ด๋์ง ์๋๋ก ๋ด๋ถ์ ์ผ๋ก ์ผ์ ๋น์จ๋งํผ ์ด๋์ ์ ํํ๊ธฐ ๋๋ฌธ์ ์ด๋ฌํ ํ์์ด ๋ฐ์ํ๋ ๊ฒ.
์๋ ์ด๋ฏธ์ง์ฒ๋ผ ์ฌ๋ผ์ด๋(range input) ์์ชฝ์ ํธ๋ค ๋๋น์ ์ ๋ฐ๋งํผ(๋ฒ์ด์ง ๊ฐ๊ฒฉ) ๊ณต๊ฐ์ ์ถ๊ฐํ๋ฉด, ํธ๋ค์ ์ค์ฌ์ด ์ฌ๋ผ์ด๋ ๋์ ์ ์ ํํ ์์นํด์ ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋๋ค.
.compare-input {
appearance: none; /* ์ฌ๋ผ์ด๋ ๊ธฐ๋ณธ ์คํ์ผ ์ ๊ฑฐ */
background: none; /* ์ฌ๋ผ์ด๋ ๋ฐฐ๊ฒฝ ์ ๊ฑฐ */
position: absolute;
inset: 0; /* ๋ค ๋ฐฉํฅ์ ์ฌ๋ฐฑ์ ๋ชจ๋ 0์ผ๋ก ์ค์ ํ๋ฉด ๋ถ๋ชจ ์์ ํฌ๊ธฐ๋งํผ ์ฑ์ */
cursor: col-resize;
/* ํธ๋ค์ ์ค์ฌ์ด ์ฌ๋ผ์ด๋ ๋์ ์ ์์นํ ์ ์๋๋ก ์กฐ์ */
width: calc(100% + var(--handle-size));
left: calc(var(--handle-size) / -2);
}
์ปค์คํ
ํธ๋ค ์์ด์ฝ์ ์ฌ์ฉํ๊ธฐ ์ํด ::-webkit-slider-thumb
๊ฐ์ ์์์ appearance: none;
์์ฑ์ ์ถ๊ฐํ์ฌ ๋ธ๋ผ์ฐ์ ๊ธฐ๋ณธ ์คํ์ผ์ ์ ๊ฑฐํ๋ค. ์ด๋ width
๊ฐ์ ์ง์ ํ์ง ์์ผ๋ฉด ํธ๋ค์ด ์ฌ๋ผ์ด๋ ํธ๋ ๋๋น๋งํผ ํ์ฅ๋๋ ํ์์ด ๋ฐ์ํ๋ฏ๋ก ์ปค์คํ
ํธ๋ค ํฌ๊ธฐ์ ๋ง๊ฒ ๋๋น๋ฅผ ์ค์ ํด์ค๋ค.
.compare-input::-webkit-slider-thumb {
appearance: none; /* Range Input ๊ธฐ๋ณธ ํธ๋ค ์คํ์ผ ์ ๊ฑฐ */
width: var(--handle-size); /* width ์ง์ ์ํ๋ฉด ํธ๋ ๊ฝ ์ฑ์ */
}
JavaScript
๋ถ๋ชจ ์์์ ์ ์ธํ CSS ๋ณ์๋ ์์ ์์์์ ์ฐธ์กฐํ ์ ์์ผ๋ฉฐ, ์์ ์์์์ ๋์ผํ ๋ณ์ ์ด๋ฆ์ผ๋ก ์ฌ์ ์(์ค๋ฒ๋ผ์ด๋)ํ ์๋ ์๋ค.
์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋๋ ๋ณต์กํ ๋ก์ง ์์ด range input ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ง ํ ๋นํ๋ฉด ๋๋ค. input ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋๋ง๋ค(์ฌ๋ผ์ด๋ ํธ๋ค ๋๋๊ทธ) --mask-width
CSS ๋ณ์ ๊ฐ์ ์
๋ฐ์ดํธํ๋ฉด, ํด๋น ๋ณ์๋ฅผ ์ฌ์ฉํ๋ ์ด๋ฏธ์ง ๋ง์คํฌ, ๊ตฌ๋ถ์ , ์ปค์คํ
ํธ๋ค์ ๋ฐ๋ก ๋ฐ์๋๋ค.
const $compare = document.querySelector(".compare");
const $compareInput = document.querySelector(".compare-input");
// range input ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค --mask-width CSS ๋ณ์ ์
๋ฐ์ดํธ
// .compare ์์ ์์์์ ํด๋น CSS ๋ณ์๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ๋ณ๊ฒฝ๋ ๊ฐ์ด ์ ์ฉ๋จ
$compareInput.addEventListener("input", (e) => {
$compare.style.setProperty("--mask-width", `${e.target.value}%`);
});
์์ฑ CodePen
See the Pen Image Comparison Slider ์ด๋ฏธ์ง ๋น๊ต ์ฌ๋ผ์ด๋ by ColorFilter (@colorfilter) on CodePen.
๋ ํผ๋ฐ์ค
Image comparison slider in 6 lines of JavaScript
Image comparison component leveraging native HTML range input and a few lines of JavaScript.
muffinman.io
<input type="range"> - HTML: HyperText Markup Language | MDN
<input> elements of type range let the user specify a numeric value which must be no less than a given value, and no more than another given value. The precise value, however, is not considered important. This is typically represented using a slider or dia
developer.mozilla.org
'๐ช Programming' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[HTTP] ETag ์ํฐํฐ ํ๊ทธ ํค๋ (0) | 2025.05.05 |
---|---|
[HTTP] Cache-Control ํค๋ (0) | 2025.03.31 |
[Next.js] API ๋ผ์ฐํธ ๋ณดํธํ๊ธฐ - Unkey (0) | 2025.03.30 |
[Next.js] dnd-kit์ ํ์ฉํ ์นธ๋ฐ(Kanban) ๋ณด๋ ๋๋๊ทธ ์ค ๋๋กญ ๊ตฌํ (0) | 2025.03.18 |
[UI] Shadcn DropdownMenu์์ Dialog ์๋ ๋ซํ ๋ฌธ์ ํด๊ฒฐ (0) | 2025.03.09 |
๋๊ธ
์ด ๊ธ ๊ณต์ ํ๊ธฐ
-
๊ตฌ๋
ํ๊ธฐ
๊ตฌ๋ ํ๊ธฐ
-
์นด์นด์คํก
์นด์นด์คํก
-
๋ผ์ธ
๋ผ์ธ
-
ํธ์ํฐ
ํธ์ํฐ
-
Facebook
Facebook
-
์นด์นด์ค์คํ ๋ฆฌ
์นด์นด์ค์คํ ๋ฆฌ
-
๋ฐด๋
๋ฐด๋
-
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
-
Pocket
Pocket
-
Evernote
Evernote
๋ค๋ฅธ ๊ธ
-
[HTTP] ETag ์ํฐํฐ ํ๊ทธ ํค๋
[HTTP] ETag ์ํฐํฐ ํ๊ทธ ํค๋
2025.05.05 -
[HTTP] Cache-Control ํค๋
[HTTP] Cache-Control ํค๋
2025.03.31 -
[Next.js] API ๋ผ์ฐํธ ๋ณดํธํ๊ธฐ - Unkey
[Next.js] API ๋ผ์ฐํธ ๋ณดํธํ๊ธฐ - Unkey
2025.03.30 -
[Next.js] dnd-kit์ ํ์ฉํ ์นธ๋ฐ(Kanban) ๋ณด๋ ๋๋๊ทธ ์ค ๋๋กญ ๊ตฌํ
[Next.js] dnd-kit์ ํ์ฉํ ์นธ๋ฐ(Kanban) ๋ณด๋ ๋๋๊ทธ ์ค ๋๋กญ ๊ตฌํ
2025.03.18