动态尺寸场景下border-image无拉伸完美填充及slice值计算方法
核心结论
border-image的slice值仅和边框素材本身的设计尺寸有关,与div的动态宽高无关。要实现无拉伸、比例正确、无缝重复的边框效果,只需要满足两个匹配规则:
slice值和素材本身设计的边框宽度完全匹配border-width的宽高比例和slice值的比例完全匹配
slice值计算逻辑
border-image采用9宫格切割逻辑:会把素材切割为四个角、四个边、中间区域共9块:
- 四个角的切片不会发生任何拉伸,直接对应div的四个角
- 四个边的切片会按照
repeat规则填充div的四条边,使用round参数时会自动调整重复次数,保证整数个切片刚好填满边的长度,无拉伸无截断,适合无缝图案 - 中间区域默认丢弃,需要填充的话可以在slice值末尾加
fill关键字
slice的四个取值(上、右、下、左)就是从素材四条边向内切割的像素值(默认无单位时为像素单位,也可以用%按素材尺寸比例计算)。
你示例中1584x1200px的素材用105 133的slice值,本质就是这个素材本身设计的上/下边框高度为105px,左/右边框宽度为133px,切割后刚好能把边框图案完整取出,所以显示正常。
通用计算步骤:
- 获取边框素材的原始宽高
- 测量素材本身的边框设计尺寸:上边框高度、右边框宽度、下边框高度、左边框宽度
- 把测量得到的四个值直接作为slice值即可,对称设计的边框可以省略重复值,上下一致就写2个值,四边一致就写1个值
- 设置border-width和slice值的比例完全对应,比如slice为
105 133,border-width就设置为105px 133px,需要响应式的话可以按比例换算为vmin等相对单位
JS动态适配方案
如果素材不固定,可以用JS动态加载素材、识别边框尺寸、自动设置样式,实现代码如下:
async function setAdaptiveBorder(divElement, borderImgUrl, customSlice = null) { // 加载边框素材获取原始尺寸 const img = new Image() img.src = borderImgUrl await new Promise(resolve => img.onload = resolve) const imgNaturalW = img.naturalWidth const imgNaturalH = img.naturalHeight let sliceTop, sliceRight, sliceBottom, sliceLeft if (customSlice) { // 有传入预设slice值直接用 [sliceTop, sliceRight = sliceTop, sliceBottom = sliceTop, sliceLeft = sliceRight] = customSlice } else { // 自动检测素材边框尺寸(仅适用于边缘为纯色/透明的标准边框素材) const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') canvas.width = imgNaturalW canvas.height = imgNaturalH ctx.drawImage(img, 0, 0) // 检测上边框高度 const edgePixel = ctx.getImageData(0, 0, 1, 1).data sliceTop = 0 for (let y = 0; y < imgNaturalH / 2; y++) { const currentPixel = ctx.getImageData(0, y, 1, 1).data const diff = Math.abs(currentPixel[0] - edgePixel[0]) + Math.abs(currentPixel[1] - edgePixel[1]) + Math.abs(currentPixel[2] - edgePixel[2]) if (diff > 10) break sliceTop = y } // 检测右边框宽度 const rightEdgePixel = ctx.getImageData(imgNaturalW - 1, 0, 1, 1).data sliceRight = 0 for (let x = imgNaturalW - 1; x > imgNaturalW / 2; x--) { const currentPixel = ctx.getImageData(x, 0, 1, 1).data const diff = Math.abs(currentPixel[0] - rightEdgePixel[0]) + Math.abs(currentPixel[1] - rightEdgePixel[1]) + Math.abs(currentPixel[2] - rightEdgePixel[2]) if (diff > 10) break sliceRight = imgNaturalW - x } sliceBottom = sliceTop sliceLeft = sliceRight } // 给元素设置样式 divElement.style.border = 'solid transparent' divElement.style.borderWidth = `${sliceTop}px ${sliceRight}px ${sliceBottom}px ${sliceLeft}px` divElement.style.borderImageSource = `url(${borderImgUrl})` divElement.style.borderImageSlice = `${sliceTop} ${sliceRight} ${sliceBottom} ${sliceLeft}` divElement.style.borderImageRepeat = 'round' } // 调用示例 const targetDiv = document.querySelector('div') // 可传入自定义slice值,比如setAdaptiveBorder(targetDiv, '素材url', [105, 133]) setAdaptiveBorder(targetDiv, 'https://cdn.dribbble.com/users/4297010/screenshots/7740500/media/7c2554596ed3249419127182c5199e65.jpg')
如果边框素材不是边缘纯色的设计,自动检测会不准,直接传入提前测量好的slice值即可。
内容的提问来源于stack exchange,提问作者Suisse
相关产品推荐
相关产品推荐

