原生JavaScript滑块如何实现窗口resize时宽度自适应
纯JS轮播滑块窗口缩放自适应修复方案
问题原因
组件存在3个核心问题导致无法跟随窗口宽度自适应:
- 滑块单页宽度
slideWidth、滑动停靠位置数组slidePositions、容器宽度仅在首次初始化时计算一次,窗口尺寸变化后没有重新计算 - 窗口resize时没有同步更新滑块的
transform偏移值,会出现位置错位、显示不全、留白溢出 - 原有代码事件兼容有bug:桌面端使用
pointer事件时仍读取e.touches[0],桌面端无touches属性会直接报错
修复方案
- 将宽度计算、位置初始化逻辑抽为独立方法,支持重复调用
- 增加窗口
resize事件监听,加防抖避免频繁触发,触发时重新计算宽度、位置数组,根据当前所在页码无动画更新滑块位置 - 兼容桌面/移动端事件坐标取值,修复事件报错问题
- 移除JS中硬写容器固定宽度的逻辑,依靠CSS弹性布局自动适配
修复后完整代码
JavaScript代码
(function() { let curTranslateX = 0; let curPageNum = 0; let dots = null; let slideWidth = 0; let duration = 300; let pointStart, pointMove, pointEnd; let slidePositions = []; let slideContainer = document.querySelector('.container_slider'); let slideWrapper = slideContainer.querySelector('.slide_wrapper'); let slideItems = [...slideWrapper.querySelectorAll('.slide-item')]; let resizeTimer = null; let negMaxWidth = 0; const utils = { hasClass: function(elem, className) { return(new RegExp('(\\s|^)' + className + '(\\s|$)')).test(elem.className); }, addClass: function(elem, className) { if(!arguments.length) return; if(typeof className === 'undefined' || this.hasClass(elem, className)) return; let newClasses = elem.className.split(' '); newClasses.push(className); elem.className = newClasses.join(' '); }, removeClass: function(elem, className) { if(!arguments.length) return; if(typeof className === 'undefined' || !this.hasClass(elem, className)) return; let classes = elem.className.split(' '); classes = classes.filter(cls => cls !== className); elem.className = classes.join(' '); }, isMobile: function() { return(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i).test(navigator.userAgent); }, getEventPoint: function(e) { return e.touches ? e.touches[0] : e; } } var slide = { calcSlideSize: function() { slideWidth = slideContainer.offsetWidth; slidePositions = []; negMaxWidth = -slideWidth * (slideItems.length - 1); for(let i = 0, wtd = 0; i < slideItems.length; i++, wtd -= slideWidth) { slidePositions.push(wtd); } }, setTranslate: function(duration, offsetX) { slideWrapper.style.transitionDuration = `${duration}ms`; slideWrapper.style.transform = `translate3d(${offsetX}px, 0px, 0px)`; curTranslateX = offsetX; }, setCurrentPage: function(num) { if(curPageNum !== -1) { utils.removeClass(dots[curPageNum], 'dot-active'); utils.removeClass(slideItems[curPageNum], 'slide-active'); } num = (typeof num === 'undefined') ? 0 : num; curPageNum = num; utils.addClass(dots[curPageNum], 'dot-active'); utils.addClass(slideItems[curPageNum], 'slide-active'); }, gotoPage: function(num, useAnimate = true) { if(num < 0 || num > dots.length - 1) return; let transDuration = useAnimate ? duration : 0; slide.setTranslate(transDuration, slidePositions[num]); setTimeout(() => { slide.setCurrentPage(num); }, transDuration / 2); }, bindTouchEvent: function() { slide.calcSlideSize(); let startX, startY, initialPos = 0, moveDist = 0, direction = 0, isMove = false, startT = 0, isPointOut = true; slideContainer.addEventListener(pointStart, function(e) { e.preventDefault(); let startPoint = utils.getEventPoint(e); if(!isPointOut && e.touches && e.touches.length !== 1) return; startX = startPoint.pageX; startY = startPoint.pageY; initialPos = curTranslateX; startT = +new Date(); isMove = false; isPointOut = false; }, false); slideContainer.addEventListener(pointMove, function(e) { if(isPointOut) return let movePoint = utils.getEventPoint(e); let deltaX = movePoint.pageX - startX; let offsetX = initialPos + deltaX; if(offsetX > 0 || offsetX < negMaxWidth) { offsetX -= (deltaX / 2); } slide.setTranslate(0, offsetX); isMove = true; deltaX = offsetX - initialPos; moveDist = deltaX; direction = deltaX > 0 ? 0 : 1; }, false); slideContainer.addEventListener(pointEnd, function(e) { e.preventDefault(); let deltaT = +new Date() - startT; if(!isMove) { if(utils.hasClass(e.target, 'slide-button-prev')) { if(curPageNum === 0) return; slide.gotoPage(curPageNum - 1); } else if(utils.hasClass(e.target, 'slide-button-next')) { if(curPageNum === dots.length - 1) return; slide.gotoPage(curPageNum + 1); } return; } if(isPointOut) return; isPointOut = true; let offsetX; if(deltaT < 300 || Math.abs(moveDist) > slideWidth / 2) { offsetX = direction === 0 ? curTranslateX + slideWidth - moveDist : curTranslateX - slideWidth - moveDist; offsetX = offsetX > 0 ? 0 : offsetX; offsetX = offsetX < negMaxWidth ? negMaxWidth : offsetX; } else { offsetX = curTranslateX - moveDist; } slide.setTranslate(duration, offsetX); let newPageNum = Math.round(Math.abs(offsetX) / slideWidth); setTimeout(() => { slide.setCurrentPage(newPageNum); }, duration / 2); }, false); window.addEventListener('resize', function() { clearTimeout(resizeTimer); resizeTimer = setTimeout(() => { slide.calcSlideSize(); slide.gotoPage(curPageNum, false); }, 200); }) }, init: (function() { document.addEventListener('DOMContentLoaded', function() { dots = [...document.querySelectorAll('.slide-navbar span')]; dots.forEach(dot => { utils.addClass(dot, 'dot'); }); if(utils.isMobile()) { pointStart = 'touchstart'; pointMove = 'touchmove'; pointEnd = 'touchend'; } else { pointStart = 'pointerdown'; pointMove = 'pointermove'; pointEnd = 'pointerup'; } slide.bindTouchEvent(); slide.setCurrentPage(); }, false); })() }; })();
CSS代码(原有样式无需修改,可直接使用)
.container_box { max-width: 1400px; margin: 0 auto; } .container_slider { position: relative; overflow: hidden; } .slide_wrapper { position: relative; z-index: 1; display: flex; transition-property: transform; } .container_slider .slide-item { z-index: 1; display: flex; flex-shrink: 0; width: 100%; height: 300px; user-select: none; background-size: cover; background-position: 50%; background-repeat: no-repeat; } .slide-navbar { position: absolute; right: 0; left: 0; bottom: 0; text-align: center; font-size: 0; z-index: 2; } .dot { display: inline-block; margin: 0 4px; width: 8px; height: 8px; border-radius: 50%; background-color: rgba(255, 255, 255, 0.5); } .dot-active { width: 20px; border-radius: 6px; background-color: rgba(233, 233, 233, .9); } .slide-button-prev, .slide-button-next { display: inline-block; position: absolute; top: 50%; width: 40px; height: 60px; z-index: 2; color: rgba(233, 233, 233, .9); text-align: center; font-weight: 500; } .slide-button-prev { left: 0; transform: translateY(-50%); } .slide-button-next { right: 0; transform: translateY(-50%); }
HTML代码(原有结构无需修改,可直接使用)
<div class="container_box"> <div class="container_slider"> <div class="slide_wrapper"> <div class="slide-item slide-active" style="background-color:green;"></div> <div class="slide-item" style="background-color:blue;"></div> <div class="slide-item" style="background-color:black;"></div> </div> <div class="control_slider slide-button-prev"><</div> <div class="control_slider slide-button-next">></div> <div class="slide-navbar"> <span class="dot dot-active">1</span> <span class="dot">2</span> <span class="dot">3</span> </div> </div> </div>
修复效果
- 窗口拉伸/缩放时自动重新计算滑块宽度,不会出现固定宽度、内容溢出或留白
- 缩放过程中保持当前所在滑块页位置,不会跳回第一页或出现错位
- 修复桌面端滑动事件报错问题,桌面端鼠标拖拽、移动端触摸滑动均可正常使用
- 防抖处理避免resize过程中频繁计算造成的卡顿
内容的提问来源于stack exchange,提问作者Salis
相关产品推荐
相关产品推荐

