如何使用CSS和JS创建可滚动、支持触控拖动的网格?
自适应不溢出正方形网格实现方案
核心布局逻辑(满足不溢出、正方形、行列数动态要求)
- 父容器前置要求:给承载网格的父级div加
min-w-0 min-h-0 overflow-hidden(Tailwind类),注意这一步是核心,否则网格作为flex/grid子项时会触发浏览器默认最小内容尺寸规则,直接撑破父容器原有尺寸,同时裁掉超出的内容,不会影响页面其他元素布局。 - 网格根容器:固定取父容器可用宽、高中的较小值作为正方形边长,永远不会超出父容器边界:
- Tailwind写法直接加类:
w-auto h-auto max-w-full max-h-full aspect-square overflow-hidden relative,其中aspect-square强制1:1比例,max-w-full/max-h-full锁死尺寸不超父容器 - 原生CSS/SCSS写法对应属性:
.grid-container { max-width: 100%; max-height: 100%; aspect-ratio: 1 / 1; overflow: hidden; position: relative; // 可选:网格在父容器内自动居中 margin: auto; }
- Tailwind写法直接加类:
- 动态行列实现:网格内层用CSS Grid布局,行列数通过JS动态传入,每格自动均分尺寸,不需要手动计算单元格宽高:
对应Tailwind给内层加// rowCount/colCount替换为业务传入的实际行列数 const gridInner = document.querySelector('.grid-inner') gridInner.style.gridTemplateColumns = `repeat(${colCount}, 1fr)` gridInner.style.gridTemplateRows = `repeat(${rowCount}, 1fr)`w-full h-full grid类即可,原生CSS对应属性为display: grid; width: 100%; height: 100%;,单元格直接加边框、填充内容就会自动适配。
跨端拖拽/滑动交互实现
不需要引入第三方库,统一封装鼠标、触摸事件逻辑即可:监听按下/触摸起点坐标,拖动时实时更新内层网格偏移量,同时阻止默认的文本选中、页面误滚动等干扰行为。
let isDragging = false let startX, startY, initialTranslateX = 0, initialTranslateY = 0 const gridContainer = document.querySelector('.grid-container') const gridInner = document.querySelector('.grid-inner') // 拖动开始逻辑 const dragStart = (clientX, clientY) => { isDragging = true startX = clientX startY = clientY // 读取当前已有的偏移量 const transform = window.getComputedStyle(gridInner).transform if (transform !== 'none') { const matrixVal = transform.match(/matrix.*\((.+)\)/)[1].split(', ') initialTranslateX = Number(matrixVal[4]) initialTranslateY = Number(matrixVal[5]) } gridInner.style.cursor = 'grabbing' } // 拖动中逻辑 const dragMove = (clientX, clientY) => { if (!isDragging) return const deltaX = clientX - startX const deltaY = clientY - startY // 可在此处追加边界判断逻辑,禁止网格拖出容器可视范围 gridInner.style.transform = `translate(${initialTranslateX + deltaX}px, ${initialTranslateY + deltaY}px)` } // 拖动结束逻辑 const dragEnd = () => { isDragging = false gridInner.style.cursor = 'grab' } // 绑定桌面端鼠标事件 gridContainer.addEventListener('mousedown', (e) => { e.preventDefault() dragStart(e.clientX, e.clientY) }) window.addEventListener('mousemove', (e) => { e.preventDefault() dragMove(e.clientX, e.clientY) }) window.addEventListener('mouseup', dragEnd) gridContainer.addEventListener('mouseleave', dragEnd) // 绑定触控端触摸事件 gridContainer.addEventListener('touchstart', (e) => { const touch = e.touches[0] dragStart(touch.clientX, touch.clientY) }, { passive: true }) gridContainer.addEventListener('touchmove', (e) => { const touch = e.touches[0] dragMove(touch.clientX, touch.clientY) }, { passive: true }) gridContainer.addEventListener('touchend', dragEnd)
- 交互优化点:鼠标事件中加
e.preventDefault()阻止默认文本选中行为,触摸事件加passive: true配置提升滑动流畅度,拖动事件绑定在window上避免鼠标移出容器后拖动状态丢失。
完整结构示例(Tailwind版)
<!-- 父容器,任意尺寸都可,网格不会撑破其原有尺寸 --> <div class="w-[80vw] h-[60vh] p-4 border flex items-center justify-center min-w-0 min-h-0 overflow-hidden"> <!-- 网格根容器,永远保持正方形,不超出父容器可用空间 --> <div class="grid-container w-auto h-auto max-w-full max-h-full aspect-square overflow-hidden cursor-grab select-none"> <!-- 网格内层,动态生成行列和单元格 --> <div class="grid-inner w-full h-full grid"> <!-- 单元格示例,实际使用时动态生成,总数量=rowCount*colCount --> <div class="border border-gray-300"></div> <div class="border border-gray-300"></div> </div> </div> </div>
如果需要适配SCSS/原生CSS,把上述类对应的样式拆出编写即可,核心逻辑完全一致。如果需要网格内容超出后支持滚动查看,只要把网格根容器的overflow-hidden改为overflow-auto,将拖动逻辑改为修改容器scrollLeft/scrollTop属性即可,不需要调整核心布局规则。
内容的提问来源于stack exchange,提问作者safeboot
相关产品推荐
相关产品推荐

