보통 input[type="range"]는 핸들러가 1개 뿐이다.
근데 만일 min, max 핸들러가 2개인 input을 구현하고 싶다면?
유의사항
- 완전 기본의 기본. 안되는게 더 많아요! 되지 않는 항목들은 다음과 같음
- 현재 min, max 제한 없이 왔다갔다함. (min이 max보다 더 오른쪽으로 이동한다든가)
- step에 대한 snap 없음
- 귀찮아서 window.resize에 대한 초기화 안 해놓음
그대로 갔다 쓰면 문제가 많을거에요 ㅋㅋ 걍 원리만 이해하시고 변형하고 추가 수정해서 쓰면 됩니다.
데모 사이트 : https://Ohikmyeong.github.io/range-slider-minmax/v1
HTML
html은 별거 없습니다. 그냥 css랑 js링크용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./css/main.css">
<script src="./js/main.js" type="module"></script>
</head>
<body>
<template>
<section class="slider-wrap">
<article class="slider-bar">
<div class="slider-bar-on"></div>
</article>
<button class="slider-btn slider-btn-min" title="min">
<span class="slider-btn-label">99999999</span>
</button>
<button class="slider-btn slider-btn-max" title="max">
<span class="slider-btn-label">999</span>
</button>
<ul class="slider-param">
<li>0</li>
<li>100</li>
<li>200</li>
<li>300</li>
<li>400</li>
<li>500</li>
<li>600</li>
</ul>
<div class="slider-amount">asdf</div>
</section><!-- slider-wrap -->
</template>
</body>
</html>
|
cs |
slider.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
@charset "utf-8";
/* wrap */
.slider-wrap{
position:relative;
width:90%;max-width:800px;
}
/* param */
.slider-param{
display:flex;
justify-content:space-between; align-items:center;
position:relative;
width:100%;
margin-top:3px;
text-align:center; font-size:12px;
}
.slider-param li{
position:relative;
min-height:20px;
border-left:1px solid #ccc;
/* padding-left:20px; */
width:calc(100% / var(--item-count));
}
.slider-param li:last-child{border-right:1px solid #ccc;}
.slider-param li::before{
content:attr(data-label-left); display:block; position:absolute;
top:calc(100% + 5px);left:0;
transform:translateX(-50%);
}
.slider-param li:last-child::after{
content:attr(data-label-right); display:block; position:absolute;
top:calc(100% + 5px);right:0;
transform:translateX(50%);
}
/* bar */
.slider-bar{
position:relative; overflow:hidden;
width:100%; height:20px;
background:#ccc;
border-radius:20px;
}
.slider-bar-on{
position:absolute;
top:0;left:0;
width:100%; height:100%;
background:royalblue;
clip-path:polygon(var(--bar-min) 0, var(--bar-max) 0, var(--bar-max) 100%, var(--bar-min) 100%);
}
/* button */
.slider-btn{
display:block;position:absolute;
left:0; top:0;
width:30px; aspect-ratio:1/1;
cursor:pointer;
background:transparent;
/* background:red; */
border:none;
}
.slider-btn::before{
content:'';display:block;position:absolute; box-sizing:border-box;
left:-15px;top:-5px;
width:100%; height:100%;
background:royalblue;
border:4px solid #fff;
border-radius:50%;
box-shadow:0 0 0 2px rgba(0,0,0,.2);
cursor:pointer;
}
/* button-label */
.slider-btn-label{
display:block;position:absolute;
left:50%;bottom:100%;
transform:translate(calc(-50% - 15px), -70%);
padding:7px 14px;
background:black;
border-radius:2px;
font-family:inherit; font-size:16px; font-weight:bold; color:#fff;
user-select:none; pointer-events:none;
}
.slider-btn-label::after{
content:'';display:block;position:absolute;
left:50%;top:100%;
transform:translateX(-50%);
border:8px solid transparent; border-top-color:black;
}
/* slider-amount */
.slider-amount{
position:relative;
margin-top:50px;
text-align:center;font-size:20px;font-weight:bold;
}
.slider-amount::before{
content:'Amount : ';
font-weight:normal;
}
|
cs |
main.js
1
2
3
4
5
6
7
|
import { SliderBuilder } from "./SliderBuilder.js";
new SliderBuilder()
.set_min(-300)
.set_max(300)
.set_step(100)
.init();
|
cs |
SliderBuilder.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
import { Slider } from "./Slider.js";
export class SliderBuilder{
constructor(){
this.info = {
min : 0,
max : 100,
step : 10,
};
}
set_min(min = 0){
this.info.min = min ?? 0;
return this;
}
set_max(max = 100){
this.info.max = max ?? 100;
return this;
}
set_step(step = 10){
this.info.step = step ?? 10;
return this;
}
init(){
return new Slider(this.info).init();
}
}//SliderBuilder
|
cs |
Slider.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
import { SliderDomMaker } from "./SliderDomMaker.js";
export class Slider extends SliderDomMaker{
constructor(info){
super();
this.info = info;
this.param = [];
this.$btnCurr = null;
this.btnPos = {
down : null,
now : null
}
}//constructor
init(){
/* DOM 그리기 */
this.draw_slider();
/* 버튼 */
this.move_btn_max_center();
/* 라벨 */
this.change_label_min(this.param[0]);
this.change_label_max(this.param[parseInt(this.param.length / 2)]);
/* 이벤트 더하기 */
this.$btnMin.addEventListener('mousedown',this.on_mouse_down,{once:true})
this.$btnMax.addEventListener('mousedown',this.on_mouse_down,{once:true})
}//init
/* ----------- */
change_label(INFO = {}){
const {value, $lbl} = INFO;
$lbl.textContent = value;
}//change_label
change_label_min(value){
this.change_label({value : value, $lbl : this.$lblMin});
}//change_label_min
change_label_max(value){
this.change_label({value : value, $lbl : this.$lblMax});
}//change_label_max
/* ----------- */
on_mouse_down = (e) =>{
this.$btnCurr = e.currentTarget;
this.btnPos.down = e.clientX;
window.addEventListener('mousemove', this.on_mouse_move);
window.addEventListener('mouseup', this.on_mouse_up, {once:true});
}//on_mouse_down
on_mouse_move = (e) => {
const xDown = e.clientX - this.btnPos.down;
const {left} = this.$btnCurr.getBoundingClientRect();
const {left:wrapLeft, width:wrapWidth} = this.$bar.getBoundingClientRect();
const leftPrev = left - wrapLeft;
const x = leftPrev + xDown;
const limitRight = wrapWidth;
let finalX = x;
if(x < 0){finalX = 0;}
if(x > limitRight){finalX = limitRight;}
this.btnPos.down = e.clientX;
/* 01. 버튼 옮기기 */
this.$btnCurr.style.transform = `translateX(${finalX}px)`;
/* 02. bar 조정 */
const perX = finalX / wrapWidth * 100;
const isMin = this.is_btn_curr_min();
this.$barOn.style.setProperty(`--bar-${isMin ? "min" : "max"}`, `calc(${perX}%)`);
/* 03. label 텍스트 변경 */
const {min,max} = this.info;
const value = parseInt(min + ((max - min) * perX / 100));
if(isMin){
this.change_label_min(value);
}else{
this.change_label_max(value);
}
/* 04.amount 텍스트 변경 */
this.display_amount();
}//on_mouse_move
on_mouse_up = (e) =>{
window.removeEventListener('mousemove',this.on_mouse_move);
this.$btnCurr.addEventListener('mousedown',this.on_mouse_down,{once:true})
this.$btnCurr = null;
}//on_mouse_up
/* ----------- */
is_btn_curr_min(){
if(this.$btnCurr.classList.contains(this.clss.btnMin)) return true;
return false;
}//is_btn_curr_min
/* ----------- */
display_amount(){
const max = Number(this.$lblMax.textContent);
const min = Number(this.$lblMin.textContent);
const amount = max - min;
this.$amount.textContent = amount;
}//display_amount
}//Slider
|
cs |
SliderDomMaker.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
export class SliderDomMaker{
constructor(){
this.clss = {
wrap : "slider-wrap",
bar : "slider-bar",
barOn : "slider-bar-on",
btn : "slider-btn",
btnMin : "slider-btn-min",
btnMax : "slider-btn-max",
btnLabel : "slider-btn-label",
param : "slider-param",
amount : "slider-amount"
}
this.$wrap = null;
this.$bar = null;
this.$barOn = null;
this.$btnMin = null;
this.$btnMax = null;
this.$lblMin = null;
this.$lblMax = null;
this.$param = null;
this.$amount = null;
this.btnWidth = 30;
}//constructor
draw_slider(){
/* wrap */
this.$wrap = this.make_wrap();
/* bar */
this.$bar = this.make_bar();
this.$wrap.appendChild(this.$bar);
/* button */
const [$btnMin, $btnMax] = this.make_btns();
this.$wrap.appendChild($btnMin);
this.$wrap.appendChild($btnMax);
/* param */
this.$param = this.make_param();
this.$wrap.appendChild(this.$param);
/* amount */
this.$amount = this.make_amount();
this.$wrap.appendChild(this.$amount);
/* 최종 */
document.body.appendChild(this.$wrap);
}//draw_slider
make_wrap(){
const $wrap = document.createElement('SECTION');
$wrap.classList.add(this.clss.wrap);
return $wrap;
}//make_wrap
make_bar(){
const $bar = document.createElement('ARTICLE');
const $barOn = document.createElement('DIV');
$bar.classList.add(this.clss.bar);
$barOn.classList.add(this.clss.barOn);
$barOn.style.setProperty('--bar-min',"0%");
$barOn.style.setProperty('--bar-max',"50%");
$bar.appendChild($barOn)
this.$barOn = $barOn;
return $bar;
}//make_bar
make_btns(){
const $btnMin = this.make_btn(true);
const $btnMax = this.make_btn(false);
return [$btnMin, $btnMax];
}//make_btns
make_btn(isMin = true){
const $btn = document.createElement('BUTTON');
$btn.classList.add(this.clss.btn);
$btn.classList.add(isMin ? this.clss.btnMin : this.clss.btnMax);
const $lbl = document.createElement('SPAN');
$lbl.classList.add(this.clss.btnLabel);
$lbl.textContent = 0;
$btn.appendChild($lbl);
if(isMin){
this.$btnMin = $btn;
this.$lblMin = $lbl;
}else{
this.$btnMax = $btn;
this.$lblMax = $lbl;
}
return $btn;
}//make_btn
make_param(){
const $param = document.createElement('UL');
$param.classList.add(this.clss.param);
const {min,max,step} = this.info;
for(let i=min; i<=max; i+=step){
if(i<max){
const $li = document.createElement('LI');
// $li.textContent = i;
$param.appendChild($li);
$li.dataset.labelLeft = i;
if(i+step >= max){$li.dataset.labelRight = i + step;}
}
this.param.push(i);
}
$param.style.setProperty('--item-count',this.param.length - 1);
return $param
}//make_param
make_amount(){
const $amount = document.createElement('DIV');
$amount.classList.add(this.clss.amount);
$amount.textContent = "0";
return $amount;
}
/* -------------- */
move_btn_max_center(){
const {width} = this.$wrap.getBoundingClientRect();
const x = (width / 2) - (this.btnWidth / 2);
this.$btnMax.style.transform = `translateX(${x}px)`;
console.log(x);
}//move_btn_max_center
}//SliderDomMaker
|
cs |
'CSS&JS > ⚡Thinkers' 카테고리의 다른 글
[JS]무작위로 LOVE 나올 때 까지 문자열 출력 (0) | 2024.05.20 |
---|---|
[JS,Canvas] 캔버스로 화살표 선 만들기 (0) | 2023.11.29 |
[JS]부모에만 mousedown,mouseup,mousemove를 적용하고 자식에는 click만 적용하고 싶을 때 (0) | 2023.08.31 |
[JS] .animate() 또는 setTimeout과 Promise로 marquee 효과 만들기 (0) | 2023.07.13 |
[JS][Canvas] 캔버스로 파이그래프 그리기 (0) | 2023.04.29 |