สร้างแถบเลื่อนเต็มหน้าแบบกำหนดเองด้วย CSS และ JavaScript
เผยแพร่แล้ว: 2022-03-11ฉันทำงานกับเลย์เอาต์เต็มหน้าจอแบบกำหนดเองเป็นประจำทุกวัน โดยปกติ เลย์เอาต์เหล่านี้บ่งบอกถึงการโต้ตอบและแอนิเมชั่นจำนวนมาก ไม่ว่าจะเป็นไทม์ไลน์ที่ซับซ้อนที่กระตุ้นเวลาของการเปลี่ยนหรือชุดเหตุการณ์ที่ผู้ใช้เลื่อนตาม ในกรณีส่วนใหญ่ UI ต้องการมากกว่าแค่การใช้โซลูชันปลั๊กอินที่พร้อมใช้งานทันทีพร้อมการปรับแต่งและการเปลี่ยนแปลงเล็กน้อย . ในทางกลับกัน ฉันเห็นนักพัฒนา JavaScript จำนวนมากมักจะเข้าถึงปลั๊กอิน JS ที่พวกเขาชื่นชอบเพื่อให้งานของพวกเขาง่ายขึ้น แม้ว่างานอาจไม่ต้องการเสียงระฆังและนกหวีดทั้งหมดที่ปลั๊กอินมีให้
ข้อจำกัดความรับผิดชอบ: การใช้ปลั๊กอินตัวใดตัวหนึ่งที่มีอยู่นั้นมีประโยชน์แน่นอน คุณจะได้รับตัวเลือกมากมายที่คุณสามารถใช้เพื่อปรับแต่งความต้องการของคุณโดยไม่ต้องเขียนโค้ดมาก นอกจากนี้ ผู้เขียนปลั๊กอินส่วนใหญ่ปรับโค้ดให้เหมาะสม ทำให้ใช้งานได้ข้ามเบราว์เซอร์และข้ามแพลตฟอร์ม และอื่นๆ แต่ถึงกระนั้น คุณจะได้รับไลบรารีขนาดเต็มรวมอยู่ในโปรเจ็กต์ของคุณสำหรับสิ่งที่แตกต่างกันเพียงหนึ่งหรือสองอย่างที่มีให้ ฉันไม่ได้บอกว่าการใช้ปลั๊กอินของบุคคลที่สามใด ๆ นั้นเป็นสิ่งที่ไม่ดีโดยธรรมชาติ ฉันทำมันทุกวันในโครงการของฉัน เพียงว่าโดยทั่วไปแล้วควรชั่งน้ำหนักข้อดีข้อเสียของแต่ละวิธีตามที่เป็นอยู่ แนวปฏิบัติที่ดีในการเข้ารหัส ในการทำสิ่งของคุณเองด้วยวิธีนี้ ต้องใช้ความรู้และประสบการณ์ในการเขียนโค้ดอีกเล็กน้อยจึงจะรู้ว่าคุณต้องการอะไร แต่สุดท้ายแล้ว คุณควรได้โค้ดชิ้นหนึ่งที่ทำสิ่งเดียวและอีกสิ่งเดียวเท่านั้น คุณต้องการให้
บทความนี้มีวัตถุประสงค์เพื่อแสดงแนวทาง CSS/JS ที่แท้จริงในการพัฒนาเลย์เอาต์ตัวเลื่อนที่ทริกเกอร์การเลื่อนแบบเต็มหน้าจอพร้อมแอนิเมชั่นเนื้อหาที่กำหนดเอง ในแนวทางที่ลดขนาดลงนี้ ฉันจะพูดถึงโครงสร้าง HTML พื้นฐานที่คุณคาดว่าจะได้รับจากส่วนหลังของ CMS เทคนิคการจัดวาง CSS (SCSS) สมัยใหม่ และการเข้ารหัส JavaScript วานิลลาเพื่อการโต้ตอบเต็มรูปแบบ ด้วยแนวคิดที่ไร้เหตุผล แนวคิดนี้สามารถขยายไปยังปลั๊กอินขนาดใหญ่ได้อย่างง่ายดาย และ/หรือใช้ในแอปพลิเคชันต่างๆ โดยไม่ต้องพึ่งพาแกนหลัก
การออกแบบที่เราจะสร้างคืองานแสดงผลงานสถาปนิกที่เรียบง่ายพร้อมรูปภาพและชื่อที่โดดเด่นของแต่ละโครงการ แถบเลื่อนที่สมบูรณ์พร้อมภาพเคลื่อนไหวจะมีลักษณะดังนี้:
คุณสามารถตรวจสอบการสาธิตได้ที่นี่ และสามารถเข้าถึง Github repo ของฉันสำหรับรายละเอียดเพิ่มเติม
ภาพรวม HTML
นี่คือ HTML พื้นฐานที่เราจะใช้งาน:
<div> <div class="mask"> <!-- Textual logo will go here --> </div> <div> <div class="slides"> <!-- Featured image slides will go here --> </div> <div class="slides mask"> <!-- Slide titles will go here --> </div> </div> <div> <!-- Static info on the right --> </div> <nav> <!-- Current slide indicator --> </nav> </div>
div ที่มีรหัสของ hero-slider
คือเจ้าของหลักของเรา ภายในเค้าโครงแบ่งออกเป็นส่วนต่างๆ:
- โลโก้ (ส่วนคงที่)
- สไลด์โชว์ที่เราจะทำกันเป็นส่วนใหญ่
- ข้อมูล (ส่วนคงที่)
- Slider nav ซึ่งจะระบุสไลด์ที่ใช้งานอยู่ในปัจจุบันรวมถึงจำนวนสไลด์ทั้งหมด
มาเน้นที่ส่วนสไลด์โชว์กันเพราะนั่นคือจุดสนใจของเราในบทความนี้ ที่นี่เรามีสองส่วน คือ main และ aux หลักคือ div ที่มีรูปภาพเด่นในขณะที่ aux เก็บชื่อรูปภาพ โครงสร้างของแต่ละสไลด์ภายในตัวยึดทั้งสองนี้ค่อนข้างเรียบง่าย เรามีสไลด์รูปภาพด้านในตัวยึดหลัก:
<div class="slide" data-index="0"> <div class="abs-mask"> <div class="slide-image"> </div> </div> </div>
แอตทริบิวต์ข้อมูลดัชนีคือสิ่งที่เราจะใช้เพื่อติดตามว่าเราอยู่ที่ใดในสไลด์โชว์ div abs-mask ที่เราจะใช้เพื่อสร้างเอฟเฟกต์การเปลี่ยนภาพที่น่าสนใจ และ div รูปภาพสไลด์มีรูปภาพเด่นเฉพาะ รูปภาพจะแสดงในบรรทัดราวกับว่ามาจาก CMS โดยตรงและกำหนดโดยผู้ใช้ปลายทาง
ในทำนองเดียวกัน ชื่อเรื่องจะเลื่อนอยู่ในที่ยึด aux:
<h2 class="slide-title slide" data-index="0"><a href="#">#64 Paradigm</a></h2>
ชื่อสไลด์แต่ละชื่อเป็นแท็ก H2 ที่มีแอตทริบิวต์ข้อมูลที่เกี่ยวข้องและลิงก์เพื่อนำไปสู่หน้าเดียวของโครงการนั้น
HTML ที่เหลือของเราค่อนข้างตรงไปตรงมาเช่นกัน เรามีโลโก้ที่ด้านบน ข้อมูลคงที่ซึ่งบอกผู้ใช้ว่าพวกเขาอยู่ในหน้าใด คำอธิบายบางส่วน และตัวเลื่อนปัจจุบัน/ตัวบ่งชี้ทั้งหมด
ภาพรวม CSS
รหัส CSS ต้นทางเขียนด้วย SCSS ซึ่งเป็นตัวประมวลผลล่วงหน้าของ CSS ซึ่งรวบรวมเป็น CSS ปกติซึ่งเบราว์เซอร์สามารถตีความได้ SCSS ช่วยให้คุณได้รับประโยชน์จากการใช้ตัวแปร การเลือกแบบซ้อน มิกซ์อิน และสิ่งที่น่าสนใจอื่นๆ แต่ต้องคอมไพล์เป็น CSS เพื่อให้เบราว์เซอร์อ่านโค้ดตามที่ควรจะเป็น สำหรับจุดประสงค์ของบทช่วยสอนนี้ ฉันได้ใช้ Scout-App เพื่อจัดการกับการคอมไพล์ เนื่องจากฉันต้องการให้มีเครื่องมือน้อยที่สุด
ฉันใช้ flexbox เพื่อจัดการเลย์เอาต์พื้นฐานแบบเคียงข้างกัน แนวคิดคือการมีสไลด์โชว์อยู่ด้านหนึ่งและส่วนข้อมูลอีกด้านหนึ่ง
#hero-slider { position: relative; height: 100vh; display: flex; background: $dark-color; } #slideshow { position: relative; flex: 1 1 $main-width; display: flex; align-items: flex-end; padding: $offset; } #info { position: relative; flex: 1 1 $side-width; padding: $offset; background-color: #fff; }
มาดูการวางตำแหน่งกัน และอีกครั้ง ให้เน้นที่ส่วนสไลด์โชว์:
#slideshow { position: relative; flex: 1 1 $main-width; display: flex; align-items: flex-end; padding: $offset; } #slides-main { @extend %abs; &:after { content: ''; @extend %abs; background-color: rgba(0, 0, 0, .25); z-index: 100; } .slide-image { @extend %abs; background-position: center; background-size: cover; z-index: -1; } } #slides-aux { position: relative; top: 1.25rem; width: 100%; .slide-title { position: absolute; z-index: 300; font-size: 4vw; font-weight: 700; line-height: 1.3; @include outlined(#fff); } }
ฉันได้ตั้งค่าตัวเลื่อนหลักให้อยู่ในตำแหน่งที่แน่นอน และให้ภาพพื้นหลังขยายพื้นที่ทั้งหมดโดยใช้คุณสมบัติ background-size: cover
เพื่อให้แตกต่างจากชื่อสไลด์มากขึ้น ฉันได้ตั้งค่าองค์ประกอบหลอกแบบสัมบูรณ์ซึ่งทำหน้าที่เป็นภาพซ้อนทับ ตัวเลื่อน aux ที่มีชื่อสไลด์จะอยู่ที่ด้านล่างของหน้าจอและด้านบนของรูปภาพ
เนื่องจากจะมองเห็นได้ครั้งละหนึ่งสไลด์เท่านั้น ฉันจึงตั้งค่าให้แต่ละชื่อเป็นแบบสัมบูรณ์เช่นกัน และให้คำนวณขนาดผู้ถือผ่าน JS เพื่อให้แน่ใจว่าไม่มีการตัดทอน แต่ให้เพิ่มเติมเกี่ยวกับเรื่องนี้ในหัวข้อต่อไปของเรา คุณสามารถดูการใช้คุณลักษณะ SCSS ที่เรียกว่าการขยายได้ที่นี่:
%abs { position: absolute; top: 0; left: 0; height: 100%; width: 100%; }
เนื่องจากฉันใช้การจัดตำแหน่งสัมบูรณ์เป็นจำนวนมาก ฉันจึงดึง CSS นี้ไปยังส่วนขยายเพื่อให้ใช้งานได้ง่ายในตัวเลือกต่างๆ นอกจากนี้ ฉันยังได้สร้างมิกซ์อินที่เรียกว่า "โครงร่าง" เพื่อจัดเตรียมแนวทางแบบแห้งเมื่อกำหนดสไตล์ของชื่อและชื่อตัวเลื่อนหลัก
@mixin outlined($color: $dark-color, $size: 1px) { color: transparent; -webkit-text-stroke: $size $color; }
สำหรับส่วนที่คงที่ของเลย์เอาต์นี้ มันไม่ได้ซับซ้อนอะไรเกี่ยวกับมัน แต่ที่นี่คุณสามารถเห็นวิธีการที่น่าสนใจเมื่อวางตำแหน่งข้อความที่ต้องอยู่บนแกน Y แทนที่จะเป็นโฟลว์ปกติ:
.slider-title-wrapper { position: absolute; top: $offset; left: calc(100% - #{$offset}); transform-origin: 0% 0%; transform: rotate(90deg); @include outlined; }
ฉันต้องการดึงความสนใจของคุณไปที่คุณสมบัติ transform-origin
เนื่องจากฉันพบว่ามันใช้งานไม่ได้มากสำหรับเลย์เอาต์ประเภทนี้ วิธีจัดตำแหน่งองค์ประกอบนี้คือจุดยึดอยู่ที่มุมซ้ายบนขององค์ประกอบ ตั้งค่าจุดหมุนและให้ข้อความไหลจากจุดนั้นลงด้านล่างอย่างต่อเนื่องโดยไม่มีปัญหากับขนาดหน้าจอต่างๆ
มาดูส่วน CSS ที่น่าสนใจกันดีกว่า - แอนิเมชั่นการโหลดเริ่มต้น:
โดยปกติ พฤติกรรมแอนิเมชั่นที่ซิงค์ประเภทนี้ทำได้โดยใช้ไลบรารี - ตัวอย่างเช่น GSAP เป็นหนึ่งในสิ่งที่ดีที่สุด ให้ความสามารถในการเรนเดอร์ที่ยอดเยี่ยม ใช้งานง่าย และมีฟังก์ชันไทม์ไลน์ที่ช่วยให้นักพัฒนาสามารถเชื่อมโยงองค์ประกอบทางโปรแกรม การเปลี่ยนแปลงเข้าสู่กันและกัน
อย่างไรก็ตาม เนื่องจากนี่เป็นเพียงตัวอย่าง CSS/JS ล้วนๆ ฉันจึงตัดสินใจใช้แบบพื้นฐานที่นี่ ดังนั้นแต่ละองค์ประกอบจะถูกตั้งค่าเป็นตำแหน่งเริ่มต้นตามค่าเริ่มต้น ไม่ว่าจะซ่อนไว้โดยการแปลงหรือความทึบ และแสดงเมื่อโหลดตัวเลื่อนซึ่งถูกทริกเกอร์โดย JS ของเรา คุณสมบัติการเปลี่ยนภาพทั้งหมดได้รับการปรับแต่งด้วยตนเองเพื่อให้แน่ใจว่ามีการไหลที่เป็นธรรมชาติและน่าสนใจ โดยการเปลี่ยนแปลงแต่ละครั้งจะดำเนินต่อไปสู่การเปลี่ยนแปลงอื่นโดยให้ประสบการณ์ภาพที่น่าพึงพอใจ
#logo:after { transform: scaleY(0); transform-origin: 50% 0; transition: transform .35s $easing; } .logo-text { display: block; transform: translate3d(120%, 0, 0); opacity: 0; transition: transform .8s .2s, opacity .5s .2s; } .current, .sep:before { opacity: 0; transition: opacity .4s 1.3s; } #info { transform: translate3d(100%, 0, 0); transition: transform 1s $easing .6s; } .line { transform-origin: 0% 0; transform: scaleX(0); transition: transform .7s $easing 1s; } .slider-title { overflow: hidden; >span { display: block; transform: translate3d(0, -100%, 0); transition: transform .5s 1.5s; } }
หากมีสิ่งหนึ่งที่ผมอยากให้คุณเห็นตรงนี้ ก็คือการใช้คุณสมบัติการท transform
ส์ฟอร์ม เมื่อย้ายองค์ประกอบ HTML ไม่ว่าจะเป็นทรานสิชั่นหรือแอนิเมชั่น ขอแนะนำให้ใช้คุณสมบัติการ transform
ฉันเห็นคนจำนวนมากที่มักจะใช้ขอบหรือช่องว่างภายใน หรือแม้แต่ออฟเซ็ต บน ซ้าย ฯลฯ ซึ่งไม่ได้ผลลัพธ์ที่เพียงพอเมื่อพูดถึงการเรนเดอร์
เพื่อให้เข้าใจในเชิงลึกยิ่งขึ้นเกี่ยวกับวิธีใช้ CSS เมื่อเพิ่มพฤติกรรมเชิงโต้ตอบ ฉันไม่สามารถแนะนำบทความต่อไปนี้ได้เพียงพอ
เป็นหน้าที่ของ Paul Lewis วิศวกร Chrome และครอบคลุมทุกอย่างที่เราควรรู้เกี่ยวกับการเรนเดอร์พิกเซลในเว็บ ไม่ว่าจะเป็น CSS หรือ JS
ภาพรวม JavaScript และลอจิกตัวเลื่อน
ไฟล์ JavaScript แบ่งออกเป็นสองฟังก์ชันที่แตกต่างกัน
ฟังก์ชัน heroSlider
ซึ่งดูแลฟังก์ชันทั้งหมดที่เราต้องการที่นี่ และฟังก์ชัน utils
ที่ฉันได้เพิ่มฟังก์ชันยูทิลิตี้ที่นำกลับมาใช้ใหม่ได้หลายฟังก์ชัน ฉันได้แสดงความคิดเห็นแต่ละฟังก์ชันยูทิลิตี้เหล่านี้เพื่อให้บริบทหากคุณต้องการนำกลับมาใช้ใหม่ในโครงการของคุณ
ฟังก์ชั่นหลักถูกเข้ารหัสในลักษณะที่มีสองสาขา: init
และ resize
สาขาเหล่านี้สามารถใช้ได้ผ่านการกลับมาของฟังก์ชันหลัก และเรียกใช้เมื่อจำเป็น init
คือการเริ่มต้นของฟังก์ชันหลักและทริกเกอร์ในเหตุการณ์การโหลดหน้าต่าง ในทำนองเดียวกัน สาขาการปรับขนาดจะถูกทริกเกอร์เมื่อปรับขนาดหน้าต่าง วัตถุประสงค์เพียงอย่างเดียวของฟังก์ชันการปรับขนาดคือการคำนวณขนาดตัวเลื่อนของชื่อเรื่องใหม่เมื่อปรับขนาดหน้าต่าง เนื่องจากขนาดแบบอักษรของชื่อเรื่องอาจแตกต่างกันไป
ในฟังก์ชัน heroSlider
ฉันได้จัดเตรียมวัตถุตัวเลื่อนซึ่งมีข้อมูลและตัวเลือกทั้งหมดที่เราต้องการ:
const slider = { hero: document.querySelector('#hero-slider'), main: document.querySelector('#slides-main'), aux: document.querySelector('#slides-aux'), current: document.querySelector('#slider-nav .current'), handle: null, idle: true, activeIndex: -1, interval: 3500 };
แนวทางนี้สามารถปรับเปลี่ยนได้อย่างง่ายดายหากคุณใช้ React เช่นการจัดเก็บข้อมูลในสถานะหรือใช้ hooks ที่เพิ่มใหม่ เพื่อให้ตรงประเด็น มาดูกันว่าคู่คีย์-ค่าแต่ละคู่หมายถึงอะไร:

- คุณสมบัติสี่ประการแรกคือการอ้างอิง HTML ไปยังองค์ประกอบ DOM ที่เราจะจัดการ
- คุณสมบัติ
handle
จะถูกใช้เพื่อเริ่มและหยุดการทำงานอัตโนมัติ - คุณสมบัติ
idle
เป็นแฟล็กที่จะป้องกันไม่ให้ผู้ใช้บังคับเลื่อนในขณะที่สไลด์อยู่ระหว่างการเปลี่ยน -
activeIndex
จะช่วยให้เราติดตามสไลด์ที่ใช้งานอยู่ในปัจจุบัน -
interval
หมายถึงช่วงเวลาเล่นอัตโนมัติของตัวเลื่อน
เมื่อเริ่มต้นตัวเลื่อน เราเรียกใช้สองฟังก์ชัน:
setHeight(slider.aux, slider.aux.querySelectorAll('.slide-title')); loadingAnimation();
ฟังก์ชัน setHeight
เอื้อมมือไปที่ฟังก์ชันยูทิลิตี้เพื่อกำหนดความสูงของตัวเลื่อน aux ของเราตามขนาดชื่อสูงสุด วิธีนี้ช่วยให้มั่นใจได้ว่ามีการปรับขนาดที่เพียงพอและจะไม่มีการตัดชื่อสไลด์แม้ว่าเนื้อหาจะเหลือสองบรรทัดก็ตาม
ฟังก์ชัน LoadAnimation เพิ่มคลาส CSS ให้กับองค์ประกอบที่มีการเปลี่ยน CSS ของอินโทร:
const loadingAnimation = function () { slider.hero.classList.add('ready'); slider.current.addEventListener('transitionend', start, { once: true }); }
เนื่องจากตัวบ่งชี้ตัวเลื่อนของเราเป็นองค์ประกอบสุดท้ายในไทม์ไลน์การเปลี่ยนแปลง CSS เราจึงรอให้การเปลี่ยนแปลงสิ้นสุดและเรียกใช้ฟังก์ชันเริ่มต้น โดยการระบุพารามิเตอร์เพิ่มเติมเป็นอ็อบเจ็กต์ เรารับรองว่าสิ่งนี้จะถูกทริกเกอร์เพียงครั้งเดียว
มาดูฟังก์ชันเริ่มต้นกัน:
const start = function () { autoplay(true); wheelControl(); window.innerWidth <= 1024 && touchControl(); slider.aux.addEventListener('transitionend', loaded, { once: true }); }
ดังนั้นเมื่อเค้าโครงเสร็จสิ้น การเปลี่ยนแปลงเริ่มต้นจะถูกทริกเกอร์โดยฟังก์ชัน loadingAnimation
และฟังก์ชันเริ่มต้นจะเข้ามาแทนที่ จากนั้นจะเรียกใช้ฟังก์ชันการเล่นอัตโนมัติ เปิดใช้งานการควบคุมวงล้อ กำหนดว่าเราอยู่บนอุปกรณ์สัมผัสหรือเดสก์ท็อป และรอให้การเลื่อนชื่อเรื่องเลื่อนในขั้นแรกเพื่อเพิ่มคลาส CSS ที่เหมาะสม
เล่นอัตโนมัติ
หนึ่งในฟีเจอร์หลักในเลย์เอาต์นี้คือฟีเจอร์เล่นอัตโนมัติ มาดูฟังก์ชันที่เกี่ยวข้องกัน:
const autoplay = function (initial) { slider.autoplay = true; slider.items = slider.hero.querySelectorAll('[data-index]'); slider.total = slider.items.length / 2; const loop = () => changeSlide('next'); initial && requestAnimationFrame(loop); slider.handle = utils().requestInterval(loop, slider.interval); }
ขั้นแรก เราตั้งค่าสถานะการเล่นอัตโนมัติเป็นจริง โดยระบุว่าตัวเลื่อนอยู่ในโหมดเล่นอัตโนมัติ แฟล็กนี้มีประโยชน์ในการพิจารณาว่าจะทริกเกอร์การเล่นอัตโนมัติอีกครั้งหลังจากที่ผู้ใช้โต้ตอบกับตัวเลื่อนหรือไม่ จากนั้นเราจะอ้างอิงรายการตัวเลื่อนทั้งหมด (สไลด์) เนื่องจากเราจะเปลี่ยนคลาสที่ใช้งานอยู่และคำนวณการวนซ้ำทั้งหมดที่ตัวเลื่อนจะมีโดยการเพิ่มรายการทั้งหมดและหารด้วยสองเนื่องจากเรามีเค้าโครงตัวเลื่อนที่ซิงค์กันสองแบบ (หลักและ aux) แต่มี "ตัวเลื่อน" ตัวเดียวเท่านั้นซึ่งเปลี่ยนทั้งคู่พร้อมกัน
ส่วนที่น่าสนใจที่สุดของโค้ดที่นี่คือฟังก์ชันลูป มันเรียกใช้ slideChange
โดยให้ทิศทางของสไลด์ซึ่งเราจะดูในนาทีนี้ อย่างไรก็ตาม ฟังก์ชันวนซ้ำถูกเรียกสองสามครั้ง มาดูกันว่าทำไม
หากอาร์กิวเมนต์เริ่มต้นถูกประเมินว่าเป็นจริง เราจะเรียกใช้ฟังก์ชันลูปเป็นการเรียกกลับ requestAnimationFrame
สิ่งนี้จะเกิดขึ้นเมื่อมีการโหลดตัวเลื่อนครั้งแรกซึ่งทำให้เกิดการเปลี่ยนแปลงสไลด์ในทันที การใช้ requestAnimationFrame
เราดำเนินการเรียกกลับที่ให้ไว้ก่อนที่จะทาสีใหม่เฟรมถัดไป
อย่างไรก็ตาม เนื่องจากเราต้องการเล่นสไลด์ต่อไปในโหมดเล่นอัตโนมัติ เราจะใช้ฟังก์ชันเดียวกันนี้ซ้ำๆ ซึ่งมักจะทำได้ด้วย setInterval แต่ในกรณีนี้ เราจะใช้หนึ่งในฟังก์ชันยูทิลิตี้– requestInterval
แม้ว่า setInterval
จะทำงานได้ดี แต่ requestInterval
เป็นแนวคิดขั้นสูงซึ่งอาศัย requestAnimationFrame
และให้แนวทางที่มีประสิทธิภาพมากกว่า ช่วยให้มั่นใจได้ว่าฟังก์ชันจะถูกทริกเกอร์อีกครั้งก็ต่อเมื่อแท็บเบราว์เซอร์ทำงานอยู่เท่านั้น
ข้อมูลเพิ่มเติมเกี่ยวกับแนวคิดนี้ในบทความที่ยอดเยี่ยมนี้สามารถพบได้ในเทคนิค CSS โปรดทราบว่าเรากำหนดค่าที่ส่งคืนจากฟังก์ชันนี้ให้กับคุณสมบัติ slider.handle
ของเรา ID เฉพาะที่ฟังก์ชันส่งคืนนี้มีให้สำหรับเรา และเราจะใช้เพื่อยกเลิกการเล่นอัตโนมัติในภายหลังโดยใช้ cancelAnimationFrame
เปลี่ยนสไลด์
ฟังก์ชัน slideChange
เป็นฟังก์ชันหลักในแนวคิดทั้งหมด มันเปลี่ยนสไลด์ไม่ว่าจะโดยการเล่นอัตโนมัติหรือโดยทริกเกอร์ผู้ใช้ รู้ทิศทางของตัวเลื่อน ให้การวนซ้ำ ดังนั้นเมื่อคุณมาถึงสไลด์สุดท้าย คุณจะสามารถไปยังสไลด์แรกได้ นี่คือวิธีที่ฉันเข้ารหัส:
const changeSlide = function (direction) { slider.idle = false; slider.hero.classList.remove('prev', 'next'); if (direction == 'next') { slider.activeIndex = (slider.activeIndex + 1) % slider.total; slider.hero.classList.add('next'); } else { slider.activeIndex = (slider.activeIndex - 1 + slider.total) % slider.total; slider.hero.classList.add('prev'); } //reset classes utils().removeClasses(slider.items, ['prev', 'active']); //set prev const prevItems = [...slider.items] .filter(item => { let prevIndex; if (slider.hero.classList.contains('prev')) { prevIndex = slider.activeIndex == slider.total - 1 ? 0 : slider.activeIndex + 1; } else { prevIndex = slider.activeIndex == 0 ? slider.total - 1 : slider.activeIndex - 1; } return item.dataset.index == prevIndex; }); //set active const activeItems = [...slider.items] .filter(item => { return item.dataset.index == slider.activeIndex; }); utils().addClasses(prevItems, ['prev']); utils().addClasses(activeItems, ['active']); setCurrent(); const activeImageItem = slider.main.querySelector('.active'); activeImageItem.addEventListener('transitionend', waitForIdle, { once: true }); }
แนวคิดคือการกำหนดสไลด์ที่ใช้งานอยู่ตามดัชนีข้อมูลที่เราได้รับจาก HTML มาพูดถึงแต่ละขั้นตอนกัน:
- ตั้งค่าสถานะไม่ได้ใช้งานของตัวเลื่อนเป็นเท็จ แสดงว่ากำลังดำเนินการเปลี่ยนสไลด์ และปิดใช้งานท่าทางล้อและการสัมผัส
- คลาส CSS ทิศทางของตัวเลื่อนก่อนหน้าถูกรีเซ็ตและเราตรวจสอบคลาสใหม่ พารามิเตอร์ทิศทางมีให้โดยค่าเริ่มต้นเป็น 'ถัดไป' หากเรามาจากฟังก์ชันเล่นอัตโนมัติหรือโดยผู้ใช้ที่เรียกใช้ฟังก์ชัน
wheelControl
หรือtouchControl
- ตามทิศทาง เราจะคำนวณดัชนีสไลด์ที่ใช้งานอยู่และจัดเตรียมคลาส CSS ของทิศทางปัจจุบันให้กับตัวเลื่อน คลาส CSS นี้ใช้เพื่อกำหนดเอฟเฟกต์การเปลี่ยนภาพที่จะใช้ (เช่น ขวาไปซ้าย หรือซ้ายไปขวา)
- สไลด์ได้รับการรีเซ็ตคลาส CSS "สถานะ" (ก่อนหน้า ใช้งานอยู่) โดยใช้ฟังก์ชันยูทิลิตี้อื่นซึ่งลบคลาส CSS แต่สามารถเรียกใช้บน NodeList ได้ แทนที่จะเป็นองค์ประกอบ DOM เดียว หลังจากนั้น เฉพาะสไลด์ก่อนหน้าและปัจจุบันเท่านั้นที่จะได้รับการเพิ่มคลาส CSS เหล่านั้น ซึ่งช่วยให้ CSS สามารถกำหนดเป้าหมายเฉพาะสไลด์เหล่านั้นและจัดเตรียมการเปลี่ยนภาพที่เพียงพอ
-
setCurrent
คือการโทรกลับที่อัปเดตตัวบ่งชี้ตัวเลื่อนตาม activeIndex - สุดท้าย เรารอให้การเปลี่ยนแปลงของสไลด์รูปภาพที่ใช้งานอยู่สิ้นสุดลงเพื่อเรียกการเรียกกลับ
waitForIdle
ซึ่งจะเริ่มเล่นอัตโนมัติใหม่หากผู้ใช้ถูกขัดจังหวะก่อนหน้านี้
การควบคุมของผู้ใช้
ตามขนาดหน้าจอ ฉันได้เพิ่มการควบคุมของผู้ใช้สองประเภท ได้แก่ วงล้อและการสัมผัส การควบคุมล้อ:
const wheelControl = function () { slider.hero.addEventListener('wheel', e => { if (slider.idle) { const direction = e.deltaY > 0 ? 'next' : 'prev'; stopAutoplay(); changeSlide(direction); } }); }
ที่นี่ เราฟังวงล้อแม้และหากตัวเลื่อนอยู่ในโหมดไม่ได้ใช้งาน (ขณะนี้ไม่ได้ทำการเปลี่ยนแปลงสไลด์) เราจะกำหนดทิศทางของล้อ เรียกใช้ stopAutoplay
เพื่อหยุดฟังก์ชั่นการเล่นอัตโนมัติหากกำลังดำเนินการอยู่ และเปลี่ยนสไลด์ตามทิศทาง ฟังก์ชัน stopAutoplay
ไม่ได้เป็นเพียงแค่ฟังก์ชันง่ายๆ ที่ตั้งค่าสถานะการเล่นอัตโนมัติของเราเป็นค่าเท็จ และยกเลิกช่วงเวลาของเราโดยเรียกใช้ฟังก์ชันยูทิลิตี้ cancelRequestInterval
ส่งผ่านหมายเลขอ้างอิงที่เหมาะสม:
const stopAutoplay = function () { slider.autoplay = false; utils().clearRequestInterval(slider.handle); }
คล้ายกับ wheelControl
เรามี touchControl
ที่ดูแลท่าทางสัมผัส:
const touchControl = function () { const touchStart = function (e) { slider.ts = parseInt(e.changedTouches[0].clientX); window.scrollTop = 0; } const touchMove = function (e) { slider.tm = parseInt(e.changedTouches[0].clientX); const delta = slider.tm - slider.ts; window.scrollTop = 0; if (slider.idle) { const direction = delta < 0 ? 'next' : 'prev'; stopAutoplay(); changeSlide(direction); } } slider.hero.addEventListener('touchstart', touchStart); slider.hero.addEventListener('touchmove', touchMove); }
เรารับฟังสองเหตุการณ์: touchstart
และ touchmove
จากนั้นเราคำนวณส่วนต่าง หากคืนค่าเป็นลบ เราจะเปลี่ยนเป็นสไลด์ถัดไปเมื่อผู้ใช้เลื่อนจากขวาไปซ้าย ในทางกลับกัน หากค่าเป็นบวก หมายความว่าผู้ใช้เลื่อนจากซ้ายไปขวา เราจะทริกเกอร์ slideChange
โดยส่งทิศทางเป็น "ก่อนหน้า" ในทั้งสองกรณี ฟังก์ชันเล่นอัตโนมัติจะหยุดลง
นี่เป็นการใช้ท่าทางสัมผัสของผู้ใช้ที่ค่อนข้างง่าย ในการสร้างสิ่งนี้ เราสามารถเพิ่มปุ่มก่อนหน้า/ถัดไปเพื่อทริกเกอร์ slideChange
เมื่อคลิก หรือเพิ่มรายการสัญลักษณ์แสดงหัวข้อย่อยเพื่อไปยังสไลด์โดยตรงตามดัชนี
บทสรุปและความคิดสุดท้ายเกี่ยวกับ CSS
ไปแล้ว วิธี CSS/JS แท้ ๆ ในการเขียนโค้ดเลย์เอาต์ตัวเลื่อนที่ไม่ได้มาตรฐานพร้อมเอฟเฟกต์การเปลี่ยนภาพที่ทันสมัย
ฉันหวังว่าคุณจะพบว่าแนวทางนี้มีประโยชน์ในฐานะวิธีคิด และสามารถใช้สิ่งที่คล้ายกันในโปรเจ็กต์ส่วนหน้าของคุณเมื่อเขียนโค้ดสำหรับโปรเจ็กต์ที่ไม่จำเป็นต้องออกแบบตามอัตภาพ
สำหรับผู้ที่สนใจเอฟเฟกต์การเปลี่ยนภาพ ฉันจะพูดถึงสิ่งนี้ในสองสามบรรทัดถัดไป
หากเราทบทวนโครงสร้าง HTML ของสไลด์ที่ฉันให้ไว้ในส่วนแนะนำ เราจะเห็นว่าแต่ละสไลด์ของรูปภาพมี div
ล้อมรอบด้วยคลาส CSS ของ abs-mask
สิ่งที่ div
นี้ทำคือการซ่อนส่วนหนึ่งของภาพที่มองเห็นได้ด้วยจำนวนหนึ่งโดยใช้ overflow:hidden
และออฟเซ็ตไปในทิศทางที่แตกต่างจากภาพ ตัวอย่างเช่น ถ้าเราดูวิธีเข้ารหัสสไลด์ก่อนหน้า:
&.prev { z-index: 5; transform: translate3d(-100%, 0, 0); transition: 1s $easing; .abs-mask { transform: translateX(80%); transition: 1s $easing; } }
สไลด์ก่อนหน้านี้มีค่าออฟเซ็ต -100% ในแกน X โดยย้ายไปทางซ้ายของสไลด์ปัจจุบัน อย่างไรก็ตาม div abs-mask
ด้านในถูกแปลไปทางขวา 80% ทำให้วิวพอร์ตแคบลง เมื่อรวมกับการมีดัชนี z ที่ใหญ่ขึ้นสำหรับสไลด์ที่ทำงานอยู่จะทำให้เกิดเอฟเฟกต์หน้าปก—ภาพที่ใช้งานจะครอบคลุมภาพก่อนหน้าในขณะเดียวกันก็ขยายพื้นที่ที่มองเห็นได้ด้วยการย้ายมาสก์ซึ่งให้มุมมองแบบเต็ม