水平時間軸(響應式+支持左滑右滑+支持左右方向鍵)
horizontalTimeLine.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no"> <title>HorizontalTimeline</title> <link rel="stylesheet" href="timeline.css"> </head> <body> <section class="timeline"> <ol> <li> <div> <time>2017</time> 這是一個水平時間軸,它有有很多優點,容我一一道來。 </div> </li> <li> <div> <time>2017</time> 響應式設計,在屏幕寬度低於 768px 的情況下,會變成垂直的時間軸。 </div> </li> <li> <div> <time>2017</time> 支持左滑右滑操作,利用 Hammer.js 實現。 </div> </li> <li> <div> <time>2017</time> 支持左右方向鍵,左方向鍵的鍵碼為 37, 而右方向鍵為 39。 </div> </li> <li> <div> <time>2017</time> 底下不用看了,因為都是復制的。 </div> </li> <li> <div> <time>1937</time> Proin quam velit, efficitur vel neque vitae, rhoncus commodo mi. Suspendisse finibus mauris et bibendum molestie. Aenean ex augue, varius et pulvinar in, pretium non nisi. </div> </li> <li> <div> <time>1937</time> Proin quam velit, efficitur vel neque vitae, rhoncus commodo mi. Suspendisse finibus mauris et bibendum molestie. Aenean ex augue, varius et pulvinar in, pretium non nisi. </div> </li> <li> <div> <time>1937</time> Proin quam velit, efficitur vel neque vitae, rhoncus commodo mi. Suspendisse finibus mauris et bibendum molestie. Aenean ex augue, varius et pulvinar in, pretium non nisi. </div> </li> <li> <div> <time>1937</time> Proin quam velit, efficitur vel neque vitae, rhoncus commodo mi. Suspendisse finibus mauris et bibendum molestie. Aenean ex augue, varius et pulvinar in, pretium non nisi. </div> </li> <li> <div> <time>1937</time> Proin quam velit, efficitur vel neque vitae, rhoncus commodo mi. Suspendisse finibus mauris et bibendum molestie. Aenean ex augue, varius et pulvinar in, pretium non nisi. </div> </li> <li> <div> <time>1937</time> Proin quam velit, efficitur vel neque vitae, rhoncus commodo mi. Suspendisse finibus mauris et bibendum molestie. Aenean ex augue, varius et pulvinar in, pretium non nisi. </div> </li> <li> <div> <time>1937</time> Proin quam velit, efficitur vel neque vitae, rhoncus commodo mi. Suspendisse finibus mauris et bibendum molestie. Aenean ex augue, varius et pulvinar in, pretium non nisi. </div> </li> <li> <div> <time>1937</time> Proin quam velit, efficitur vel neque vitae, rhoncus commodo mi. Suspendisse finibus mauris et bibendum molestie. Aenean ex augue, varius et pulvinar in, pretium non nisi. </div> </li> <li> <div> <time>1937</time> Proin quam velit, efficitur vel neque vitae, rhoncus commodo mi. Suspendisse finibus mauris et bibendum molestie. Aenean ex augue, varius et pulvinar in, pretium non nisi. </div> </li> <li> <div> <time>1937</time> Proin quam velit, efficitur vel neque vitae, rhoncus commodo mi. Suspendisse finibus mauris et bibendum molestie. Aenean ex augue, varius et pulvinar in, pretium non nisi. </div> </li> <li></li> </ol> <div class="arrows"> <button class="arrow arrow-pre disabled"> <img src="pre.svg" alt="pre timeline arrow"> </button> <button class="arrow arrow-next"> <img src="next.svg" alt="next timeline arrow"> </button> </div> </section> <script src="https://cdn.bootcss.com/hammer.js/2.0.8/hammer.min.js"></script> <script src="timeline.js"></script> </body> </html>
timeline.css
* { margin: 0; padding: 0; box-sizing: border-box; } /*box-sizing:content-box是默認值。 意思是:邊框border和內邊距padding的值是包含在被作用的標簽的width和height之內的。 也就是說,如果你將一個元素的width設為100px,那么這100px會包含其它的border和padding, 內容區的實際寬度會是width減去(border + padding)的計算值。 但是,要注意它不包括外邊距margin的值在內。 大多數情況下這使得我們更容易的去設定一個元素的寬高。 */ body { background-color: #456990; line-height: : 1.5; } /*“::selection”用來改變在瀏覽器中選中文本后的設置。*/ /*如,被選中后的背景色為紅色,被選中后的字體顏色為藍色*/ ::selection { background-color: red; color: blue; } /*white-space:它的屬性用來設置如何處理元素內的空白。 默認值為,nornmal,空白會被瀏覽器給忽略。 pre,空白會被瀏覽器保留,同<pre>標簽的作用類似。 nowrap,文本不會換行,在同一行上繼續顯示,直到遇到換行符。 pre-wrap,保留空白,但是 正常地進行換行。*/ /*overflow-x,用來設置,如果元素中內容的左右邊緣,溢出元素的內容區域的話怎么辦? auto:如果溢出框,則應該提供滾動機制。 hidden: 裁剪內容 - 不提供滾動機制。 scroll:裁剪內容 - 提供滾動機制。*/ .timeline { white-space: nowrap; overflow-x: hidden; } .timeline ol { font-size: 0; padding: 250px 0; width: 100vw; transition: all 1s; } .timeline ol li { position: relative; display: inline-block; width: 160px; height: 3px; list-style-type: none; background-color: #fff; } .timeline ol li:last-child { width: 280px; } .timeline ol li:not(:first-child) { margin-left: 14px; } .timeline ol li:not(:last-child)::after { content: ""; position: absolute; top: 50%; transform: translateY(-50%); left: calc(101%); width: 12px; height: 12px; border-radius: 50%; background-color: #F45B69; } .timeline ol li div { position: absolute; left: calc(107%); width: 280px; padding: 15px; font-size: 1rem; white-space: normal; color: #333; background-color: #fff; } .timeline ol li div::before { content: ''; position: absolute; top: 100%; left: 0; width: 0; height: 0; border-style: solid; } .timeline ol li:nth-child(odd) div { top: -16px; transform: translateY(-100%); } .timeline ol li:nth-child(odd) div::before { top: 100%; border-width: 8px 8px 0 0 ; border-color: #fff transparent transparent transparent; } .timeline ol li:nth-child(even) div { top: 16px; } .timeline ol li:nth-child(even) div::before { top: -8px; border-width: 0 8px 8px 0; border-color: transparent transparent #fff transparent; } .timeline div.arrows { display: flex; justify-content: center; margin-bottom: 20px; } .timeline div.arrows button { border: none; background-color: #F45B69; width: 48px; height: 48px; border-radius: 50%; outline: none; cursor: pointer; } .timeline div.arrows button.arrow-pre { margin-right: 20px; } .timeline div.arrows button.disabled { opacity: .5; } @media screen and (max-width: 768px) { .timeline ol { width: auto; padding: 0; transform: 0 !important; } .timeline ol li { width: auto; height: auto; display: block; background-color: transparent; } .timeline ol li:first-child { margin-top: 20px; } .timeline ol li:not(:first-child) { margin-left: auto; } .timeline ol li div { width: 94%; height: auto !important; margin: 0 auto 25px; position: static; } .timeline ol li:nth-child(odd) div { transform: none; } .timeline ol li:nth-child(odd) div::before, .timeline ol li:nth-child(even) div::before, .timeline ol li:not(:last-child)::after { display: none; } .timeline div.arrows { display: none; } }
timeline.js
const timeline = document.querySelector('.timeline ol'), elH = document.querySelectorAll('.timeline ol div'), arrows = document.querySelectorAll('.arrows .arrow'), arrowPrev = document.querySelector('.timeline .arrows .arrow-pre'), arrowNext = document.querySelector('.timeline .arrows .arrow-next'), firstItem = document.querySelector('.timeline li:first-child'), lastItem = document.querySelector('.timeline ol li:last-child'), xScrolling = 280, disabledClass = 'disabled'; window.addEventListener('load', init, false); function init() { setEqualHeight(elH); arrowPrev.disabled = true; animateTl(xScrolling, arrows, timeline); setSwipeFn(timeline, arrowPrev, arrowNext); setKeyboardFn(arrowPrev, arrowNext); } function setEqualHeight(el) { var largestHeight = (el[0] && el[0].offsetHeight) || 0, length = el.length; for(var i = 1; i < length; i++) { if (el[i].offsetHeight > largestHeight) { largestHeight = el[i].offsetHeight; } } for(var i = 0; i < length; i++) { el[i].style.height = largestHeight + 'px'; } } function animateTl(scrolling, el, tl) { for(var i = 0; i < el.length; i++) { var count = 0; el[i].addEventListener('click', function() { if (!arrowPrev.disabled) { arrowPrev.disabled = true; } if (!arrowNext.disabled) { arrowNext.disabled = true; } var sign = (this.classList.contains('arrow-pre')) ? "" : "-"; if (count === 0) { tl.style.transform = "translateX( -" + scrolling + 'px )'; } else { var tlStyle = getComputedStyle(tl), values = parseInt(tlStyle.getPropertyValue('transform').split(',')[4]) + parseInt(sign + scrolling); tl.style.transform = 'translateX(' + values + 'px )'; } count += 1; setTimeout(()=> { isElementInViewport(firstItem) ? setBtnState(arrowPrev) : setBtnState(arrowPrev, false); isElementInViewport(lastItem) ? setBtnState(arrowNext) : setBtnState(arrowNext, false); }, 1100); }, false); } } function isElementInViewport(el) { var rect = el.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } function setBtnState(el, flag = true) { if (flag) { el.classList.add(disabledClass); } else { if (el.classList.contains(disabledClass)) { el.classList.remove(disabledClass); } el.disabled = false; } } function setSwipeFn(tl, prev, next) { const hammer = new Hammer(tl); hammer.on('swipeleft', ()=>{ next.click(); }); hammer.on('swiperight', ()=>{ prev.click(); }); } function setKeyboardFn(prev, next) { document.addEventListener('keydown', (e)=> { if (e.which === 37 || e.which === 39) { var timelineOffsetTop = timeline.offsetTop, y = window.pageYOffset; if (timelineOffsetTop !== y) { window.scrollTo(0, timelineOffsetTop); } if (e.which === 37) { prev.click(); } if (e.which === 39) { next.click(); } } }); }