CSS&JS/👀Study and Copy

[OnlineTutorials] 3d Carousel Slider(drag,touch,button)

arancia_ 2023. 9. 7. 15:49

https://www.youtube.com/watch?v=ikbwJy84f2Y 

영상과 코드가 다릅니다. 아이템의 갯수가 변화할시 일일히 CSS 수정 안 하고 걍 JS가 알아서 계산해서 CSS 대입하도록 했음.

핵심 개념
핵심 개념(2) CSS transform의 순서 중요

 

See the Pen 3d carousel by Oh Ikmyeong (@dpffpself) on CodePen.

HTML

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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3d Gallery</title>
    <link rel="stylesheet" href="./style.css">
    <script src="./main.js" type="module"></script>
</head>
<body>
    <a href="https://www.youtube.com/watch?v=ikbwJy84f2Y" target="_blank">3D Image Gallery in CSS & Javascript</a>
 
    <div id="scene">
        <ul id="gall">
            <li class="gall-item">1</li>
            <li class="gall-item">2</li>
            <li class="gall-item">3</li>
            <li class="gall-item">4</li>
            <li class="gall-item">5</li>
            <li class="gall-item">6</li>
            <li class="gall-item">7</li>
            <li class="gall-item">8</li>
            <li class="gall-item">9</li>
            <li class="gall-item">10</li>
        </ul>
    </div>
    <div id="btns">
        <button class="btn" data-direction="prev">PREV</button>
        <button class="btn" data-direction="next">NEXT</button>
    </div>
</body>
</html>
cs

CSS

아이템들을 중앙에 위치하게 하기 위해선, 아이템의 position은 absolute로,
아이템을 감싼 직속 부모의 크기는 아이템의 크기와 같도록 한다.

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
@charset "utf-8";
*{margin:0;padding:0;box-sizing:border-box;}
li{list-style-type:none;}
html,body{
    width:100%;
}
body{
    display:flex;flex-flow:column nowrap;
    justify-content:center; align-items:center;
    gap:60px;
    min-height:100vh;
    background:#666;
}
 
/* scene */
#scene{
    display:flex; 
    justify-content:center;align-items:center;
    position:relative; overflow-x:hidden;
    width:100%; aspect-ratio:16/9;
    perspective:100vmax;
    background:#000;
}
 
/* gall */
#gall{
    outline:1px solid red;
    position:relative;
    background:red;
    width:30vmin; aspect-ratio:5/3;
    max-width:250px;
    transform-style:preserve-3d;
}
 
.gall-item{
    display:flex;
    justify-content:center; align-items:center;
    position:absolute;
    width:100%; height:100%;
    background:rgba(255,255,255,.6);
    border:2px solid salmon;
    font-size:5vmin;
    backdrop-filter:blur(5px);
    user-select:none;
    -webkit-box-reflect:below 0px linear-gradient(transparent,#0004);
}
 
/*  */
#btns{
    position:relative;
}
.btn{
    font-family:inherit;
    padding:1em 3em;
}
cs

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
const $gall = document.getElementById('gall');
const $$item = document.querySelectorAll('.gall-item');
const $btns = document.getElementById('btns');
const deg = 360 / $$item.length;
let gallDeg = 0;
const POS = {
    x : 0
}
 
/** 배치 */
function style_item(){
    const itemWidth = $$item[0].offsetWidth;
    const getZ = get_z({deg, itemWidth});
    $$item.forEach(($item,idx)=>{
        $item.style.transform = `rotateY(${idx * deg}deg) translateZ(${getZ}px)`;
    });
}//style_item
 
/** 거리 구해오기 */
function get_z(INFO = {}){
    const {deg,itemWidth} = INFO;
    const widthHalf = (itemWidth / 2);
    const radient = (Math.PI / 180 ) * (deg / 2);
    const r =  widthHalf / Math.tan(radient);
    const gap = Math.min(window.innerWidth, window.innerHeight) / 50;
    const result = parseInt(r);
    console.log(radient, Math.tan(radient), result);
    return result + gap;
}//get_z
 
/** 마우스 다운 */
function on_mouse_down(e){
    POS.x = e.clientX;
    window.addEventListener('mouseup',on_mouse_up,{once:true});
}//on_mouse_down
 
/** 터치 시작 */
function on_touch_start(e){
    POS.x = e.touches[0].clientX;
    window.addEventListener('touchend',on_touch_end,{once:true});
}
 
/** 마우스 업 */
function on_mouse_up(e){
    const newX = e.clientX - POS.x;
    cacul_final_deg(newX);
    $gall.addEventListener('mousedown', on_mouse_down, {once:true});
}//on_mouse_up
 
/** 터치 끝 */
function on_touch_end(e){
    const newX = e.changedTouches[0].clientX - POS.x;
    cacul_final_deg(newX);
    $gall.addEventListener('touchstart',on_touch_start,{once:true});
}//on_touch_end
 
/**
 * 회전시킬지 말지 정해서 gallDeg값 변경
 * @param {Number} newX 
 * @returns 
 */
function cacul_final_deg(newX){
    if(Math.abs(newX) < 50return;
    const finalDeg = newX > 0 ?  deg : -1 * deg ;
    gallDeg += finalDeg;
    rotate_gallery();
}//cacul_final_deg
 
/**
 * 갤러리 회전시키기
 */
function rotate_gallery(){
    $gall.animate([{
        transform : `rotateY(${gallDeg}deg)`,
    }],{
        duration : 1000,
        fill : "both",
        easing : "ease-in-out"
    });
}//rotate_gallery
 
/* --------------------------- */
/* 실행 */
style_item();
$gall.addEventListener('mousedown', on_mouse_down, {once:true});
$gall.addEventListener('touchstart',on_touch_start,{once:true});
 
/* 윈도우 창 변경시 */
window.addEventListener('resize',()=>{
    style_item();
});
 
/* 버튼으로 움직이기 */
$btns.addEventListener('click',(e)=>{
    if(e.target.tagName != "BUTTON"return;
    const finalDeg = e.target.dataset.direction == "prev" ?  deg : -1 * deg ;
    gallDeg += finalDeg;
    rotate_gallery();
});
cs