如何通过JS事件检测用户滚动至电影滑块末尾?
滚动触发加载:解决横向flexbox列表的“到达末尾”检测问题
我正在做一个大学电影票务网站项目,首页需要实现滚动加载功能:
- 用户先看到10部电影
- 滚动到电影列表末尾时,通过fetch请求额外5部电影
- 加载完成后展示15部,支持继续滚动
现在卡在第二步:不知道怎么给横向滚动的flexbox(#movie-slider)添加“到达末尾”的触发事件,求解决办法。
现有代码示例
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Movie Tickets</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Teko:wght@300&display=swap" rel="stylesheet"> <link rel="stylesheet" href="/css/index.css"> <link rel="stylesheet" href="/css/general.css"> </head> <body> <article class="content"> <section id="movie-slider"> </section> </article> <script src="/js/index.js" type="module"></script> <script src="/js/general.js" type="module"></script> </body> </html>
CSS
body { margin: 0; font-family: 'Teko', sans-serif; } .float-right { float: right; } .content { transition: margin-top 0.4s; } .content { background: fixed; background-image: url("/images/homepage_background.jpg"); background-position: center; background-size: cover; min-height: 100vh; } #movie-slider { display: flex; flex-direction: row; align-items: end; overflow: auto; gap: 10px; padding: 10vh 10%; } .movie-banner { margin-top: 50px; margin-bottom: 150px; flex-basis: 250px; flex-shrink: 0; display: flex; flex-direction: column; } .movie-banner__image { object-fit: cover; height: 400px; } .movie-banner__title { color: wheat; text-align: center; font-family: 'Bebas Neue', cursive; font-size: 1.5em; padding: 10px } .movie-banner__desc { color: wheat; font-size: 1.3em; text-align: center; }
JavaScript
class Movie { constructor(title, image, description, duration, release, age, schedule, seats) { this.title = title; this.image = image; this.description = description; this.duration = duration; this.release = release; this.age = age; this.schedule = schedule; //dictionary met alle tijden per dag. this.seats = seats; } } const blankMovie = new Movie( "Blank", "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Grey_background.jpg/636px-Grey_background.jpg?20140113173544", "blank", "0 minutes", 100 ); var allMovies = []; for (let i = 0; i < 10; i++) { allMovies.push(blankMovie) } function populatewith(movies) { const homePage = document.getElementById("movie-slider"); for (let i = 0; i < allMovies.length; i++) { const movieDiv = document.createElement("div"); movieDiv.className = "movie-banner"; const movieTitle = document.createElement("span"); movieTitle.className = "movie-banner__title"; movieTitle.textContent = allMovies[i].title; const movieImage = document.createElement("img"); movieImage.className = "movie-banner__image"; movieImage.src = allMovies[i].image; const movieDur = document.createElement("span"); movieDur.className = "movie-banner__desc"; movieDur.textContent = allMovies[i].duration; homePage.appendChild(movieDiv); movieDiv.appendChild(movieTitle); movieDiv.appendChild(movieImage); movieDiv.appendChild(movieDur); } } populatewith(allMovies);
解决方案
核心思路
要检测横向滚动容器是否到达末尾,需监听容器的scroll事件,通过三个属性的数值对比判断:
scrollLeft:容器已滚动的水平距离clientWidth:容器的可视宽度scrollWidth:容器内容的总宽度(含不可见部分)
当scrollLeft + clientWidth >= scrollWidth - 阈值时,即可判定到达列表末尾,触发加载逻辑。
具体实现
1. 添加滚动监听与末尾检测
给#movie-slider容器添加scroll事件监听,同时加入加载锁避免重复请求:
const movieSlider = document.getElementById('movie-slider'); let isLoading = false; // 加载锁,防止同一时间多次发起请求 movieSlider.addEventListener('scroll', () => { // 预留20px提前触发阈值,提升用户体验 const threshold = 20; const isAtEnd = movieSlider.scrollLeft + movieSlider.clientWidth >= movieSlider.scrollWidth - threshold; if (isAtEnd && !isLoading) { isLoading = true; loadMoreMovies(); } });
2. 实现加载更多逻辑
编写异步加载函数,请求新数据并增量渲染到列表:
async function loadMoreMovies() { try { // 替换为你的实际API地址,用allMovies.length作为偏移量 const response = await fetch(`/api/movies?offset=${allMovies.length}`); const newMovies = await response.json(); // 将新电影添加到全局数组 allMovies.push(...newMovies); // 只渲染新增的电影,避免全量重绘 renderNewMovies(newMovies); isLoading = false; // 加载完成,解锁 } catch (error) { console.error('加载电影失败:', error); isLoading = false; // 出错后也要解锁,保证后续能触发加载 } } // 增量渲染函数,仅处理新电影 function renderNewMovies(movies) { const homePage = document.getElementById("movie-slider"); movies.forEach(movie => { const movieDiv = document.createElement("div"); movieDiv.className = "movie-banner"; const movieTitle = document.createElement("span"); movieTitle.className = "movie-banner__title"; movieTitle.textContent = movie.title; const movieImage = document.createElement("img"); movieImage.className = "movie-banner__image"; movieImage.src = movie.image; const movieDur = document.createElement("span"); movieDur.className = "movie-banner__desc"; movieDur.textContent = movie.duration; homePage.appendChild(movieDiv); movieDiv.appendChild(movieTitle); movieDiv.appendChild(movieImage); movieDiv.appendChild(movieDur); }); }
3. 优化初始渲染函数
修改原populatewith函数,避免重复渲染全部内容:
function populatewith(movies) { const homePage = document.getElementById("movie-slider"); homePage.innerHTML = ''; // 清空容器,防止重复初始化 movies.forEach(movie => { const movieDiv = document.createElement("div"); movieDiv.className = "movie-banner"; const movieTitle = document.createElement("span"); movieTitle.className = "movie-banner__title"; movieTitle.textContent = movie.title; const movieImage = document.createElement("img"); movieImage.className = "movie-banner__image"; movieImage.src = movie.image; const movieDur = document.createElement("span"); movieDur.className = "movie-banner__desc"; movieDur.textContent = movie.duration; homePage.appendChild(movieDiv); movieDiv.appendChild(movieTitle); movieDiv.appendChild(movieImage); movieDiv.appendChild(movieDur); }); }
内容的提问来源于stack exchange,提问作者Jord van Eldik
相关产品推荐
相关产品推荐

