JS30 day27 Click and Drag to Scroll

Posted by Anthony Chao on 2019-11-27

JS30 day27 - Click and Drag to Scroll

作業內容

這次的作業是做出可以用滑鼠左右拉讓螢幕滑動的效果
一樣 code 本身不複雜,但要想出怎麼做出來是需要創意的
可以參考這份 CodePen的效果

學到什麼

  • JS

    1. 細節:在抓點下去時候的 X 座標,我們抓 mouseDown 事件的 pageX 屬性
      但如果把這個 pageX console.log 出來看會發現問題,如果 margin 變大,可以發現就算點在 slider 邊邊,他的起始座標就是 100 多而不是接近0,這是為什麼?
      因為這 pageX 是在這頁面中的絕對座標,我們要的是相對於這個捲動頁面框框的座標,所以要把整個框框的 left 座標減掉
    1
    2
    3
    4
    5
    6
    7
    slider.addEventListener('mousedown', (e)=> {
    isDown = true
    slider.classList.add('active')
    startX = e.pageX - slider.offsetLeft
    scollLeft = slider.scrollLeft // 要記錄一開始 slider 已經捲了多少
    console.log(e.pageX)
    })
    1. 細節: slider 觸發的 mousemove 事件這邊加上 preventDeault 是因為怕觸發意料之外的效果,像是選到文字等等,這個很容易漏掉
    1
    2
    3
    4
    5
    6
    7
    slider.addEventListener('mousemove', (e)=> {
    if(!isDown) return; // 如果沒有按滑鼠不觸發
    e.preventDefault() // 避免捲動的時候去選框框內文字
    const x = e.pageX - slider.offsetLeft
    walk = x - startX
    slider.scrollLeft = scollLeft - walk
    })
    1. 有個很細節的地方在於,如果導覽列上方有東西,我們必須把座標值扣掉,否則白框位置會跑掉
    1
    2
    3
    4
    5
    6
    const coords = {
    height: dropdownCoords.height,
    width: dropdownCoords.width,
    top: dropdownCoords.top - navCoords.top,
    left: dropdownCoords.left - navCoords.left
    }

    dropdownCoords.top - navCoords.top,像這行就是下拉選單的座標扣掉導覽列的上方座標

    1. 除此之外還有更細節的地方,一開始程式碼長的是下面這樣
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function handleEnter(){
    this.classList.add('trigger-enter')
    setTimeout(() => {
    this.classList.add('trigger-enter-active')
    },150)
    //下略
    }
    function handleLeave(){
    this.classList.remove('trigger-enter', 'trigger-enter-active')
    }
    //下略

    我再加上 class 的時候為了有動畫的效果,有個 setTimeout 設定 150 微秒之後才加上 trigger-enter-active 這 class,但移除的時候是同時移除
    這樣會有個問題,當我在導覽列中快速移動,可能已經觸發到移除了,但 150 微秒還沒到,所以後面下拉選單的內容才跑出來
    解決方法如下:再設上一個條件,150微秒的時候檢查是不是有一開始加上的那個 class trigger-enter,沒有的話就不用再加上這個 class

    1
    2
    3
    4
    5
    6
    7
    8
    function handleEnter(){
    this.classList.add('trigger-enter')
    setTimeout(() => {
    if(this.classList.contains('trigger-enter')){
    this.classList.add('trigger-enter-active')
    }
    },150)
    //下略

https://github.com/wesbos/JavaScript30

code 內容:

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
<div class="items">
<div class="item item1">01</div>
<div class="item item2">02</div>
<div class="item item3">03</div>
<div class="item item4">04</div>
<div class="item item5">05</div>
<div class="item item6">06</div>
<div class="item item7">07</div>
<div class="item item8">08</div>
<div class="item item9">09</div>
<div class="item item10">10</div>
<div class="item item11">11</div>
<div class="item item12">12</div>
<div class="item item13">13</div>
<div class="item item14">14</div>
<div class="item item15">15</div>
<div class="item item16">16</div>
<div class="item item17">17</div>
<div class="item item18">18</div>
<div class="item item19">19</div>
<div class="item item20">20</div>
<div class="item item21">21</div>
<div class="item item22">22</div>
<div class="item item23">23</div>
<div class="item item24">24</div>
<div class="item item25">25</div>
</div>

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
html {
box-sizing: border-box;
background: url('https://source.unsplash.com/NFs6dRTBgaM/2000x2000') fixed;
background-size: cover;
}

*, *:before, *:after {
box-sizing: inherit;
}

body {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: sans-serif;
font-size: 20px;
margin: 0;
}

.items {
height: 800px;
padding: 100px;
width: 100%;
border: 1px solid white;
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
user-select: none;
cursor: pointer;
transition: all 0.2s;
transform: scale(0.98);
will-change: transform;
position: relative;
background: rgba(255,255,255,0.1);
font-size: 0;
perspective: 500px;
}

.items.active {
background: rgba(255,255,255,0.3);
cursor: grabbing;
cursor: -webkit-grabbing;
transform: scale(1);
}

.item {
width: 200px;
height: calc(100% - 40px);
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 80px;
font-weight: 100;
color: rgba(0,0,0,0.15);
box-shadow: inset 0 0 0 10px rgba(0,0,0,0.15);
}

.item:nth-child(9n+1) { background: dodgerblue;}
.item:nth-child(9n+2) { background: goldenrod;}
.item:nth-child(9n+3) { background: paleturquoise;}
.item:nth-child(9n+4) { background: gold;}
.item:nth-child(9n+5) { background: cadetblue;}
.item:nth-child(9n+6) { background: tomato;}
.item:nth-child(9n+7) { background: lightcoral;}
.item:nth-child(9n+8) { background: darkslateblue;}
.item:nth-child(9n+9) { background: rebeccapurple;}

.item:nth-child(even) { transform: scaleX(1.31) rotateY(40deg); }
.item:nth-child(odd) { transform: scaleX(1.31) rotateY(-40deg); }

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
const slider = document.querySelector('.items')
let isDown = false
let startX
let scrollLeft

slider.addEventListener('mousedown', (e)=> {
isDown = true
slider.classList.add('active')
startX = e.pageX - slider.offsetLeft // 要減掉 slider 本身的位移減掉
scollLeft = slider.scrollLeft // 要記錄一開始 slider 已經捲了多少
console.log(e.pageX)
})
slider.addEventListener('mouseleave', ()=> {
isDown = false
slider.classList.remove('active')
})
slider.addEventListener('mouseup', ()=> {
isDown = false
slider.classList.remove('active')
})
slider.addEventListener('mousemove', (e)=> {
if(!isDown) return; // 如果沒有按滑鼠不觸發
e.preventDefault() // 避免捲動的時候去選框框內文字
const x = e.pageX - slider.offsetLeft
walk = x - startX
slider.scrollLeft = scollLeft - walk
})




prevent_hack