前言
前不久,在我的一個(gè)項(xiàng)目中,需要展示一個(gè)橫向滾動(dòng)的標(biāo)簽頁(yè),它支持鼠標(biāo)橫向拖動(dòng)和點(diǎn)擊切換。在實(shí)現(xiàn)的過程中,我發(fā)現(xiàn)這個(gè)小功能需要同時(shí)用到前端的三輛馬車,但是實(shí)現(xiàn)難度不高,而且最終效果還不錯(cuò),是個(gè)難得的初學(xué)者項(xiàng)目,于是萌生了寫這篇文章的想法,希望對(duì)初學(xué)者有所幫助。同時(shí)為了避免初學(xué)者學(xué)習(xí)框架,我打算用純?cè)姆绞綄?shí)現(xiàn)它。
我們最終的效果應(yīng)該類似于下面:
需求分析
需求分析就是細(xì)化我們需要完成的功能,某個(gè)功能的完成需要哪些技術(shù)的參與。對(duì)于初學(xué)者,需求分析至關(guān)重要,它可以幫助我們理清思路,找到解決問題的突破口,所以應(yīng)該引起足夠的重視。以本篇目標(biāo)為例,標(biāo)簽頁(yè)的需求分析就可以像下面這樣:
- 我們的展示主體是標(biāo)簽頁(yè),HTML就是實(shí)現(xiàn)主體的主要技術(shù);
- 標(biāo)簽頁(yè)需要可以拖動(dòng)和點(diǎn)擊,這涉及到鼠標(biāo)事件的監(jiān)聽和處理,是JS的主場(chǎng);
- 既然標(biāo)簽頁(yè)可以拖動(dòng)了,那是否要隱藏那個(gè)丑陋的滾動(dòng)條,加個(gè)活動(dòng)指示器,給鼠標(biāo)變一個(gè)樣式?很明顯,這些都是CSS的優(yōu)勢(shì)。
如上,通過對(duì)展示,操作,樣式的劃分,我們進(jìn)一步明確了HTML,JS,CSS需要完成的工作,甚至連實(shí)現(xiàn)都明朗了,所以對(duì)需求拆分得越詳細(xì),對(duì)實(shí)現(xiàn)就越有掌控力。
基本框架
對(duì)于前端來說,HTML始終是萬(wàn)物之源,所以一言不合先構(gòu)筑個(gè)標(biāo)準(zhǔn)的HTML頁(yè)面總是沒錯(cuò)的。為了便于演示,我將所有的內(nèi)容都放在一個(gè)HTML文件中,文件結(jié)構(gòu)如下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tab演示</title>
<!-- 這里是樣式區(qū),后續(xù)css代碼會(huì)添加到這里 -->
<style type="text/css">
</style>
</head>
<body>
<!-- 這里是頁(yè)面區(qū),后續(xù)HTML代碼會(huì)添加到這里 -->
</body>
<!-- 這里是腳本區(qū),后續(xù)JS代碼會(huì)添加到這里,放在這里是因?yàn)榉奖銓懘a -->
<script type="text/javascript">
</script>
</html>
這里和以往不同,我將script
放到了最后,這是因?yàn)槲蚁朐趯懩_本的時(shí)候,頁(yè)面標(biāo)簽直接可用,減少對(duì)頁(yè)面加載的監(jiān)聽,降低復(fù)雜性。
實(shí)現(xiàn)基本功能
有了基本結(jié)構(gòu),下一步當(dāng)然是畫頁(yè)面啦。從效果圖中不難看出,頁(yè)面主要包括一個(gè)一個(gè)的選項(xiàng)卡,對(duì)于HTML來說,這不就是列表嘛。于是,突破口就出現(xiàn)了,我們先往HTML里面加入列表
<ul>
<li>肖申克的救贖</li>
<li>霸王別姬</li>
<li>阿甘正傳</li>
<li>泰坦尼克號(hào)</li>
<li>這個(gè)殺手不太冷</li>
<li>美麗人生</li>
<li>千與千尋</li>
<li>辛德勒的名單</li>
<li>盜夢(mèng)空間</li>
<li>忠犬八公的故事</li>
</ul>
于是,我們有了原始的標(biāo)簽頁(yè)。但是標(biāo)簽頁(yè)是豎向的,并且有著丑陋的小黑點(diǎn),不符合需求。
發(fā)現(xiàn)了這些問題,下一步當(dāng)然解決這些問題了,這當(dāng)然就是CSS的強(qiáng)項(xiàng)啦。首要問題就是讓列表橫過來。橫過來就是改變了元素的相對(duì)位置,也就是對(duì)應(yīng)CSS的布局功能。那說起布局,CSS的布局方式有很多,像float
,position
等等。標(biāo)簽頁(yè)是橫向多個(gè)緊密排列的,一個(gè)挨著一個(gè),這當(dāng)然是用flex
啦。至于討厭的小黑點(diǎn),這是新東西,需要百度一下。查閱文檔發(fā)現(xiàn),ul
有個(gè)屬性list-style-type
,只需把它設(shè)置為none
就可以去除小黑點(diǎn)。
此時(shí),頁(yè)面上的所有選項(xiàng)卡都緊密排列了。為了讓它更像一個(gè)選項(xiàng)卡,需要給它居中,限制一下寬度,加個(gè)背景色,加點(diǎn)padding。下面就是改完樣式的代碼
ul{
display: flex;
justify-content: center;
align-content: center;
list-style-type: none;
background-color: #2397f3;
width: 600px;
overflow-x: scroll;
}
li{
padding: 16px;
flex-shrink: 0;
}
值得注意的地方有兩點(diǎn)。在ul
的樣式中,由于給ul
加了寬度限制,導(dǎo)致它的內(nèi)容超出了內(nèi)容區(qū),所以要給ul
加上overflow-x
的屬性。同樣由于寬度的原因,flex
子項(xiàng)在寬度不夠的情況下會(huì)默認(rèn)縮小,表現(xiàn)在標(biāo)簽上就是文字換行啦,flex-shrink: 0;
就是讓子項(xiàng)保留原有大小。此時(shí),再來刷新頁(yè)面,可以看到選項(xiàng)卡的基本雛形已經(jīng)出來了。雖然簡(jiǎn)陋,但是可以拖動(dòng)滾動(dòng)條左右滾動(dòng)了。下一步,我們的目標(biāo)就是去除這個(gè)丑陋的滾動(dòng)條。網(wǎng)上搜索一番,發(fā)現(xiàn)火狐,IE和Chrome的方式不盡相同,為了兼容性,我們就都給寫上。
ul{
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
}
ul::-webkit-scrollbar {
display: none; /* Chrome Safari */
}
滾動(dòng)條去除后,UI好看了,但是新問題出現(xiàn)了——選項(xiàng)卡滾動(dòng)不了了。別著急,下一步就是添加鼠標(biāo)拖動(dòng)功能。
實(shí)現(xiàn)交互
在瀏覽器中,HTML標(biāo)簽有對(duì)系統(tǒng)事件的監(jiān)聽能力,響應(yīng)這些事件,可以使頁(yè)面實(shí)時(shí)響應(yīng)用戶的操作。通過對(duì)不同的事件的組合,可以實(shí)現(xiàn)各種豐富、有趣的功能,標(biāo)簽頁(yè)也一樣。
標(biāo)簽頁(yè)的首要功能是可以隨著鼠標(biāo)的拖動(dòng)而滾動(dòng)元素,那么,首要任務(wù)就是監(jiān)聽鼠標(biāo)的移動(dòng)事件啦。但是光監(jiān)聽移動(dòng)還不行,因?yàn)橥ǔ碚f,用戶在鼠標(biāo)左鍵按下后才希望真正拖動(dòng),鼠標(biāo)左鍵抬起后結(jié)束拖動(dòng)。所以,這個(gè)拖動(dòng)動(dòng)作其實(shí)需要組合鼠標(biāo)按下(mousedown
),移動(dòng)(mousemove
),抬起(mouseup
)三個(gè)事件。那么這三個(gè)方法加在哪,怎么加呢?
在Web API中,JS操作HTML的入口點(diǎn)是Document
對(duì)象,Document
提供了操作(增刪改查)HTML元素的API。這一過程是有標(biāo)準(zhǔn)流程的。
- 通過
Document
查找目標(biāo)元素; - 對(duì)目標(biāo)元素進(jìn)行元素,樣式變更等操作;
- 變更完成;
這一過程是重復(fù)且繁雜的,為了減少編寫這樣的樣板代碼,加快開發(fā)速度,一大堆前端框架應(yīng)運(yùn)而生。所以,在學(xué)習(xí)前端框架時(shí),牢記這一基本步驟,有助于快速理解框架的運(yùn)行原理。畢竟無(wú)論框架怎么變,最終都是要落實(shí)到這一過程上。
算法明確后,接下來就是具體實(shí)現(xiàn)。
查找目標(biāo)元素
在查找目標(biāo)前,需要首先明確目標(biāo)是誰(shuí)。用戶肯定不希望在頁(yè)面的其他地方拖動(dòng)鼠標(biāo),標(biāo)簽頁(yè)跟著滾動(dòng)了,這很奇怪。所以我們的目標(biāo)元素應(yīng)該是無(wú)序列表。那么,怎樣通過Document
知道無(wú)序列表呢,查閱Document
的API,發(fā)現(xiàn)它有個(gè)querySelector
的方法,這個(gè)方法會(huì)從上到下查找滿足條件的選擇器,并返回第一個(gè)滿足條件的元素,參數(shù)則是選擇器的名稱。上面已經(jīng)明確過我們的目標(biāo)是無(wú)序列表,所以查找目標(biāo)元素的最終代碼如下
const ul=document.querySelector('ul');
為列表滾起來
每一個(gè)HTML元素,在JS中都是Element
的對(duì)象。上一步我們已經(jīng)得到了一個(gè)Element
對(duì)象ul
,注意,這里的ul
對(duì)象和ul
標(biāo)簽不盡相同。一個(gè)是JS的對(duì)象對(duì)HTML標(biāo)簽的表示,一個(gè)是HTML標(biāo)簽?,F(xiàn)在有了一個(gè)對(duì)象,那么就可以通過調(diào)用合適的方法來操作這個(gè)對(duì)象了。通過查閱Element
對(duì)象的API,發(fā)現(xiàn)它有個(gè)addEventListener()
的方法,這個(gè)方法可以完成該對(duì)象表示的HTML標(biāo)簽對(duì)某些事件的監(jiān)聽。這個(gè)方法接收兩個(gè)參數(shù),第一個(gè)參數(shù)是事件名稱,這在上一節(jié)已經(jīng)說過。第二個(gè)參數(shù)則是對(duì)這個(gè)事件的處理,這也是我們實(shí)現(xiàn)魔法的地方。
首先,在用戶按下鼠標(biāo)左鍵后,開始記錄鼠標(biāo)移動(dòng)情況。在鼠標(biāo)左鍵抬起后,停止記錄。所以按下和抬起的主要功能就是維護(hù)記錄開關(guān),控制標(biāo)簽滾動(dòng)的動(dòng)作得在鼠標(biāo)移動(dòng)的回調(diào)里處理。
但在真正寫邏輯前,還有兩個(gè)問題沒有處理。
1、怎樣讓標(biāo)簽滾動(dòng)?
2、滾動(dòng)的邏輯怎樣寫?
問題一當(dāng)然需要查閱Element
的API啦。搜索滾動(dòng)相關(guān)的,發(fā)現(xiàn)兩個(gè)相關(guān)性比較大的方法——scrollBy()
,scrollTo()
,都可以滾動(dòng)內(nèi)容。唯一的區(qū)別是前者的參數(shù)是滾動(dòng)的偏移,后者是最終值。由于鼠標(biāo)移動(dòng)是一點(diǎn)一點(diǎn)的,所以選擇前者會(huì)更方便一點(diǎn)。確定了方法,也就解答了問題一。對(duì)于問題二,簡(jiǎn)單來說就是怎樣提供問題一所需的參數(shù)。scrollBy()
需要兩個(gè)參數(shù),橫向和縱向的滾動(dòng)偏移值,由于我們只希望標(biāo)簽頁(yè)可以橫向滾動(dòng),所以縱向的偏移始終是0,那么橫向的呢?通常事件回調(diào)都會(huì)傳遞一個(gè)事件對(duì)象,稱作MouseEvent
,我們?nèi)ゲ椴槭录?duì)象的API,發(fā)現(xiàn)里面帶有好幾個(gè)關(guān)于坐標(biāo)的屬性——clientX
,movementX
,screenX
。movementX
直接就滿足我們的需求,它代表上一次鼠標(biāo)移動(dòng)到這一次移動(dòng)間的偏移,而剛好scrollBy()
需要的參數(shù)就是偏移,妥了。
綜上,得出以下代碼
const ul=document.querySelector('ul');
let isMouseDown=false;
ul.addEventListener('mousedown',(e)=>{
isMouseDown=true;
})
ul.addEventListener('mousemove',(e)=>{
if(isMouseDown){
ul.scrollBy(-e.movementX,0);
}
})
ul.addEventListener('mouseup',(e)=>{
isMouseDown=false;
})
可以看到,在mousemove
的處理上,偏移加了個(gè)負(fù)號(hào)。因?yàn)樵贖TML頁(yè)面中左上角為坐標(biāo)原點(diǎn),右邊為X軸正方向。一直往右,則X坐標(biāo)是增大的,而movementX
的值是當(dāng)前鼠標(biāo)坐標(biāo)與上一次坐標(biāo)點(diǎn)的差值,上一次肯定比這一次小,兩者的差值肯定是正值?;谕瑯拥脑?,scrollBy()
參數(shù)正值代表增大X值,也就是顯示右邊的內(nèi)容,隱藏左邊的內(nèi)容。兩者結(jié)合的效果就是,鼠標(biāo)往右拖,標(biāo)簽頁(yè)右邊隱藏的內(nèi)容展示了出來,這和直覺相悖。通常我們希望鼠標(biāo)往右拖,頁(yè)面展示左邊的內(nèi)容,隱藏右邊的。基于這樣的分析,我們需要給movementX
的值取反。
顯示當(dāng)前選中的標(biāo)簽頁(yè)
現(xiàn)在,標(biāo)簽頁(yè)可以滾動(dòng)了,但是還不能選中。我希望點(diǎn)擊某個(gè)標(biāo)簽時(shí),標(biāo)簽下方出現(xiàn)一個(gè)小橫條表示選中狀態(tài)。很明顯,顯示小橫條是一個(gè)CSS的問題,而點(diǎn)擊標(biāo)簽切換小橫條是JS的問題,這一次我們需要同時(shí)處理JS和CSS的問題。
首先來顯示小橫條。顯示小橫條有兩個(gè)思路,一種是在HTML中搞個(gè)div
標(biāo)簽,另一種是使用::after
偽元素。我選擇后一種,這樣可以保持HTML的干凈。
接下來需要確定小橫條的樣式
- 覆蓋在選中的標(biāo)簽上
- 位置是標(biāo)簽底部
- 和標(biāo)簽一樣長(zhǎng)
我們知道正常的HTML文檔流是從左到右,從上到下的,新加的元素會(huì)追加到已有元素的右邊或者下邊。小橫條需要覆蓋在標(biāo)簽上,那么就要改變這一默認(rèn)行為,position
屬性就是實(shí)現(xiàn)這個(gè)功能的關(guān)鍵。absolute
,fixed
都可以脫離正常文檔流,使元素覆蓋在祖先元素上,不同的是前者是相對(duì)于最近的定位祖先,后者是相當(dāng)于視口的。小橫條是跟隨著標(biāo)簽顯示的,顯然要使用前者。確定了位置,還有大小和樣式。既然使用了絕對(duì)定位,那么bottom
,'left'
,right
相應(yīng)就能限定它的位置和大小了,小橫條的樣式就直接用border-bottom
吧。于是,小橫條的樣式就出來了
.current::after{
content: "";
position: absolute;
border-bottom: 4px solid #FFC109;
border-radius: 2px;
bottom: 0;
left: 0;
right: 0;
}
結(jié)束了嗎,還沒有!使用了絕對(duì)定位,必須時(shí)刻記得給絕對(duì)定位的元素找個(gè)錨點(diǎn),也就是參照,不然top
,left
,right
,bottom
去參考誰(shuí)呢?那么怎樣告訴絕對(duì)定位的參照物呢,還是position
屬性。只不過這一次它要出現(xiàn)在參照物的CSS里面。而由前面的樣式分析,小橫條始終跟著標(biāo)簽頁(yè)走,也就是說小橫條的參照物就是標(biāo)簽頁(yè)。所以,還要在標(biāo)簽頁(yè)的樣式上加上position
的屬性。當(dāng)然,為了區(qū)分更明顯,我還改變了一下顏色。
.current{
color: white;
position: relative;
}
至此,小橫條可以正常顯示出來了。
小橫條跟隨鼠標(biāo)點(diǎn)擊顯示
有了前面拖動(dòng)功能的經(jīng)驗(yàn)支持,這一次輕車熟路了,鼠標(biāo)點(diǎn)擊某個(gè)標(biāo)簽頁(yè),小橫條顯示在對(duì)應(yīng)的標(biāo)簽頁(yè)下方。這一次事件的對(duì)象變成了單個(gè)標(biāo)簽頁(yè),所以點(diǎn)擊事件要加在單個(gè)標(biāo)簽頁(yè)上。但是這一次標(biāo)簽頁(yè)太多了,我們不能還是按照之前的查找-設(shè)置方法,這樣太繁雜了。巧合的是,前面我們已經(jīng)得到了ul
對(duì)象了,通過它的children
屬性,可以得到所有的li
,這不就妥了嗎。
小橫條要切換到不同的標(biāo)簽頁(yè)上顯示,也就是小橫條這個(gè)樣式要根據(jù)點(diǎn)擊對(duì)象的不同而動(dòng)態(tài)增加或者刪除。查閱Element
的API,發(fā)現(xiàn)有個(gè)className
的屬性,改變它的值就可以增減樣式了。
let last=null;
for(let l of ul.children){
l.addEventListener('click',(e)=>{
if(last){
last.className='';
}
e.target.className='current';
last=e.target;
})
}
代碼的實(shí)現(xiàn)中,多了個(gè)last
對(duì)象。因?yàn)橥ǔ?biāo)簽頁(yè)只能同時(shí)選中一個(gè),當(dāng)新的標(biāo)簽頁(yè)被選中之后,上一個(gè)選中的標(biāo)簽頁(yè)應(yīng)該恢復(fù)原始樣式,這就是last
對(duì)象的作用。我們先取消選中上一個(gè)元素,然后再選中當(dāng)前點(diǎn)擊的對(duì)象,這樣就完成了小橫條跟隨點(diǎn)擊選中的效果了。
總結(jié)
總的來說,這個(gè)項(xiàng)目的難點(diǎn)不在于實(shí)現(xiàn)有多難,而是新。很多初學(xué)者,面對(duì)這種新問題往往束手無(wú)策,找不到切入點(diǎn)。本篇嘗試以例子的形式,以初學(xué)者的思維方式分析需求,拆解問題,提煉方法,最終解決問題。從最樸素的直覺出發(fā),引導(dǎo)思考,找到一條易于接受和理解的方法。
所以,遇到新問題不要慌,對(duì)問題拆解后,看能不能找到突破口,如果找不到,再?gòu)纳婕暗降膸讉€(gè)主要對(duì)象中尋找靈感,通常都會(huì)有所收獲。最后就是多逛逛MDN,關(guān)鍵時(shí)刻真能派上大用場(chǎng)。
最后,情人節(jié)快樂,祝有情人終成眷屬!文章來源:http://www.zghlxwxcb.cn/news/detail-481286.html
參考文章來源地址http://www.zghlxwxcb.cn/news/detail-481286.html
- [1] 使用CSS隱藏元素滾動(dòng)條
- [2] Element
- [3] 事件參考
- [4] MouseEvent
到了這里,關(guān)于用純HTML,JS,CSS實(shí)現(xiàn)橫向滾動(dòng)標(biāo)簽頁(yè)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!