一、问题本质
在 Retina 等高分辨率屏幕上,设备像素比(Device Pixel Ratio,DPR)通常为 2 或 3。这意味着:
- CSS 像素是逻辑像素
- 物理像素 = CSS像素 × DPR
- 设置
border: 1px实际上会渲染成 2 或 3 个物理像素
二、核心解决方案
方案1:媒体查询 + 伪元素 + transform(最推荐)
/* 基础样式 */
.border-1px {
position: relative;
}
/* 四条边框 */
.border-1px::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 200%;
height: 200%;
border: 1px solid #e5e5e5;
transform: scale(0.5);
transform-origin: 0 0;
pointer-events: none;
box-sizing: border-box;
}
/* 下边框 */
.border-bottom-1px {
position: relative;
}
.border-bottom-1px::after {
content: '';
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 1px;
background: #e5e5e5;
}
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) {
.border-bottom-1px::after {
transform: scaleY(0.5);
transform-origin: 0 bottom;
}
}
@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 3dppx) {
.border-bottom-1px::after {
transform: scaleY(0.333);
transform-origin: 0 bottom;
}
}
方案2:使用 viewport + rem 方案
<!-- 在HTML头部加入 -->
<script>
const dpr = window.devicePixelRatio || 1
const scale = 1 / dpr
const metaEl = document.createElement('meta')
metaEl.setAttribute('name', 'viewport')
metaEl.setAttribute('content',
`width=device-width,initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale},user-scalable=no`)
document.documentElement.firstElementChild.appendChild(metaEl)
// 设置根字体大小
document.documentElement.style.fontSize = `${100 * dpr}px`
</script>
方案3:使用 border-image
.border-image-1px {
border-width: 1px 0 0 0;
border-image: url('data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="1"><rect x="0" y="0" width="100" height="0.5" fill="%23e5e5e5"/></svg>') 2 0 stretch;
}
/* 针对不同DPR优化 */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) {
.border-image-1px {
border-image-source: url('data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="1"><rect x="0" y="0" width="100" height="0.5" fill="%23e5e5e5"/></svg>');
}
}
@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 3dppx) {
.border-image-1px {
border-image-source: url('data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="1"><rect x="0" y="0" width="100" height="0.333" fill="%23e5e5e5"/></svg>');
}
}
方案4:使用 background 渐变
/* 单边框 - 上边框 */
.border-top-gradient {
background-image: linear-gradient(to bottom, #e5e5e5, #e5e5e5 50%, transparent 50%);
background-size: 100% 1px;
background-repeat: no-repeat;
background-position: top;
}
/* 四边框 */
.border-all-gradient {
background-image:
linear-gradient(to bottom, #e5e5e5, #e5e5e5 50%, transparent 50%),
linear-gradient(to right, #e5e5e5, #e5e5e5 50%, transparent 50%),
linear-gradient(to top, #e5e5e5, #e5e5e5 50%, transparent 50%),
linear-gradient(to left, #e5e5e5, #e5e5e5 50%, transparent 50%);
background-size: 100% 1px, 1px 100%, 100% 1px, 1px 100%;
background-position: top, right, bottom, left;
background-repeat: no-repeat;
}
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) {
.border-top-gradient {
background-size: 100% 0.5px;
}
.border-all-gradient {
background-size: 100% 0.5px, 0.5px 100%, 100% 0.5px, 0.5px 100%;
}
}
方案5:使用 box-shadow
.box-shadow-1px {
box-shadow: 0 0 0 1px #e5e5e5;
}
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) {
.box-shadow-1px {
box-shadow: 0 0 0 0.5px #e5e5e5;
}
}
@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 3dppx) {
.box-shadow-1px {
box-shadow: 0 0 0 0.333px #e5e5e5;
}
}
三、PostCSS 插件自动化方案
使用 postcss-write-svg 或 postcss-px-to-viewport 自动处理:
// postcss.config.js
module.exports = {
plugins: {
'postcss-write-svg': {
uft8: false
},
'postcss-px-to-viewport': {
viewportWidth: 750,
unitPrecision: 3,
viewportUnit: 'vw',
selectorBlackList: ['.ignore'],
minPixelValue: 1,
mediaQuery: false
}
}
}
/* 自动生成 1px 边框 */
@svg border {
height: 2px;
@rect {
fill: var(--color, black);
width: 100%;
height: 50%;
}
}
.example {
border: 1px solid transparent;
border-image: svg(border param(--color #00b1ff)) 2 2 stretch;
}
四、Vue/React 组件方案
Vue 组件示例
<template>
<div :class="['border-1px', borderPosition]" :style="styleObject">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'Border1px',
props: {
color: {
type: String,
default: '#e5e5e5'
},
position: {
type: String,
default: 'bottom' // top, bottom, left, right, all
}
},
computed: {
borderPosition() {
return `border-${this.position}`
},
styleObject() {
return {
'--border-color': this.color
}
}
}
}
</script>
<style scoped>
/* 样式同上,使用 CSS 变量 */
.border-bottom::after {
background: var(--border-color, #e5e5e5);
}
</style>
五、移动端框架解决方案
1. Vant 框架方案
// 使用 Vant 的 Cell 组件,自带 1px 边框处理
import { Cell } from 'vant'
2. Ant Design Mobile 方案
// 使用 List.Item,自带 1px 边框处理
import { List } from 'antd-mobile'
六、实践建议
根据项目选择方案:
- 新项目:使用 viewport + rem 方案,一劳永逸
- 现有项目改造:使用 transform + 伪元素方案
- 组件库开发:使用 PostCSS 插件自动化处理
- 小程序:直接使用 rpx 单位(1px = 2rpx)
最佳实践组合:
/* mixin 示例 */
@mixin border-1px($color: #e5e5e5, $position: bottom) {
position: relative;
@if $position == 'bottom' {
&::after {
content: '';
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 1px;
background: $color;
@media (-webkit-min-device-pixel-ratio: 2) {
transform: scaleY(0.5);
}
@media (-webkit-min-device-pixel-ratio: 3) {
transform: scaleY(0.333);
}
}
}
/* 其他方向类似 */
}
/* 使用 */
.item {
@include border-1px(#ccc, bottom);
}
注意事项:
- transform-origin 要正确设置
- 伪元素 z-index 处理
- 背景色覆盖问题
- 圆角边框需要特殊处理
- 性能考量:transform 性能最好
选择方案时,建议团队统一标准,避免多种方案混用。对于复杂场景,可以组合使用多种方案。