如何解决1px的问题

7 阅读3分钟

一、问题本质

在 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'

六、实践建议

根据项目选择方案:

  1. 新项目:使用 viewport + rem 方案,一劳永逸
  2. 现有项目改造:使用 transform + 伪元素方案
  3. 组件库开发:使用 PostCSS 插件自动化处理
  4. 小程序:直接使用 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);
}

注意事项:

  1. transform-origin 要正确设置
  2. 伪元素 z-index 处理
  3. 背景色覆盖问题
  4. 圆角边框需要特殊处理
  5. 性能考量:transform 性能最好

选择方案时,建议团队统一标准,避免多种方案混用。对于复杂场景,可以组合使用多种方案。