前言
移动端web项目越来越多,在开发过程中经常遇到UI设计师对像素的要求越来越高,在 Retina
屏幕中1px会比在web端要粗的多,因为css中的1px并不等于移动设备的1px,这是由于不同的手机有不同的像素密度照成的。
移动端 1px 像素问题是什么
举个例子,iphone6 的屏幕宽度为 375px,设计师做的视觉稿一般是 750px,也就是 2x 图,这个时候设计师在设计稿上画了 1px 的边框,如果你在 css 中也写了border-width: 1px
,问题就出现了。 先来看一下出现的问题吧
div {
width: 200px;
height: 200px;
}
.box1 {
border-top: 1px solid #000;
}
.box2 {
position: relative;
border-top: 1px solid #000;
}
<div class="box1">1px上边框</div>
<div class="box2">0.5px上边框</div>
复制代码
效果 :
那么为什么会发生这样的变化呢?
原因:
- 在
retina
屏的手机上,dpr
为2
或3
,css
里写的1px
宽度映射到物理像素上就有2px
或3px
宽度。 iPhone6
的dpr
为2
,物理像素是750
(x轴),它的逻辑像素为375
。也就是说,1个逻辑像素,在x
轴和y
轴方向,需要2个物理像素来显示,即:dpr=2时,表示1个CSS像素由4个物理像素点组成。
可能很多人就不明白了 dpr
是什么,物理像素是什么,逻辑像素又是什么
实际上 设备像素比:dpr=window.devicePixelRatio,也就是设备的物理像素与逻辑像素的比值
接下来就说一下什么是物理像素,什么是逻辑像素(枯燥乏味的知识)
物理像素
物理像素是指像素点的多少,而不是指具体的物理尺寸。显示屏是由一个个物理像素点组成的,通过控制每个像素点的颜色,使屏幕显示出不同的图像,屏幕从工厂出来那天起,它上面的物理像素点就固定不变了。
我们常说的分辨率指的就是物理像素,比如 iphone6s plus 的分辨率是 1920x1080
,表示横向有 1920 个物理像素,竖向有 1080 个物理像素。
逻辑像素(设备独立像素)
设备独立像素是一个虚拟的单位,独立于设备,是用于逻辑上衡量长度的单位,由底层系统的程序使用,会由相关系统转换为物理像素。
一种形象的说法,可以理解为在 css 中的 px。设备独立像素 = 逻辑像素 = CSS 像素
设备独立像素有什么用?
举个例子,iphone3 和 iphone4 的尺寸都是 3.5 寸,但 iphone3 的分辨率是 320x480,iphone4 的分辨率是 640x960,这也就是意味着同样长度的屏幕,iphone3 有 320 个物理像素,iphone4 有 640 个物理像素。
如果我们按照真实的物理像素进行布局,比如说我们按照 320 物理像素进行布局,到了 640 物理像素的手机上就会有一半的空白,为了避免这种问题,就产生了虚拟像素单位(设备独立像素)。
我们统一 iphone3 和 iphone4 都是 320 个虚拟像素,只是在 iphone3 上,最终 1 个虚拟像素换算成 1 个物理像素,在 iphone4 中,1 个虚拟像素最终换算成 2 个物理像素。
至于 1 个虚拟像素被换算成几个物理像素,这个数值我们称之为设备像素比。
提示
iPhone6 分辨率 750 x 1334(物理像素),设备独立像素 375 x 667,设备像素比为 2。
iPhone6 plus 分辨率 1242 x 2208(物理像素),设备独立像素 414 x 736,设备像素比为 3。
解决方案
利用 css 的 伪元素::after
+ transfrom
进行缩放
实现全部边框
/* 全部边框 */
.box3 {
position: relative;
}
.box3::after {
content: '';
position: absolute;
box-sizing: border-box;
top: 0;
left: 0;
width: 200%;
height: 200%;
border: 1px solid #000;
border-radius: 4px;
transform: scale(0.5);
transform-origin: left top;
}
.box4 {
margin-top: 20px;
border: 1px solid #000;
}
<div class="box3">0.5px全部边框</div>
<div class="box4"> 1px全部边框 </div>
复制代码
注意要加上这段代码:
transform-origin: left top;
,不然会出现虚化效果。
效果如下:
实现单上边框:
.box2 {
position: relative;
/* border-top: 1px solid #000; */
}
/* 单个上边框 */
.box2::after {
content: '';
box-sizing: border-box;
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 1px;
background-color: #000;
transform: scaleY(0.5);
transform-origin: left top;
}
<div class="box1">1px上边框</div>
<div class="box2">0.5px上边框</div>
复制代码
效果如下:
开源组件库解决方案
ant-design-mobile 组件库
@mixin scale-hairline-common($color, $top, $right, $bottom, $left) {
content: '';
position: absolute;
display: block;
z-index: 1;
top: $top;
right: $right;
bottom: $bottom;
left: $left;
background-color: $color;
}
@mixin hairline($direction, $color: #000, $radius: 0) {
@if $direction == top {
border-top: 1px solid $color;
// min-resolution 用来检测设备的最小像素密度
@media (min-resolution: 2dppx) {
border-top: none;
&::before {
@include scale-hairline-common($color, 0, auto, auto, 0);
width: 100%;
height: 1px;
transform-origin: 50% 50%;
transform: scaleY(0.5);
@media (min-resolution: 3dppx) {
transform: scaleY(0.33);
}
}
}
} @else if $direction == right {
border-right: 1px solid $color;
@media (min-resolution: 2dppx) {
border-right: none;
&::after {
@include scale-hairline-common($color, 0, 0, auto, auto);
width: 1px;
height: 100%;
background: $color;
transform-origin: 100% 50%;
transform: scaleX(0.5);
@media (min-resolution: 3dppx) {
transform: scaleX(0.33);
}
}
}
} @else if $direction == bottom {
border-bottom: 1px solid $color;
@media (min-resolution: 2dppx) {
border-bottom: none;
&::after {
@include scale-hairline-common($color, auto, auto, 0, 0);
width: 100%;
height: 1px;
transform-origin: 50% 100%;
transform: scaleY(0.5);
@media (min-resolution: 3dppx) {
transform: scaleY(0.33);
}
}
}
} @else if $direction == left {
border-left: 1px solid $color;
@media (min-resolution: 2dppx) {
border-left: none;
&::before {
@include scale-hairline-common($color, 0, auto, auto, 0);
width: 1px;
height: 100%;
transform-origin: 100% 50%;
transform: scaleX(0.5);
@media (min-resolution: 3dppx) {
transform: scaleX(0.33);
}
}
}
} @else if $direction == all {
border: 1px solid $color;
border-radius: $radius;
@media (min-resolution: 2dppx) {
position: relative;
border: none;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 200%;
height: 200%;
border: 1px solid $color;
border-radius: $radius * 2;
transform-origin: 0 0;
transform: scale(0.5);
box-sizing: border-box;
pointer-events: none;
}
}
}
}
// 移除边框
@mixin hairline-remove($position: all) {
@if $position == left {
border-left: 0;
&::before {
display: none !important;
}
} @else if $position == right {
border-right: 0;
&::after {
display: none !important;
}
} @else if $position == top {
border-top: 0;
&::before {
display: none !important;
}
} @else if $position == bottom {
border-bottom: 0;
&::after {
display: none !important;
}
} @else if $position == all {
border: 0;
&::before {
display: none !important;
}
&::after {
display: none !important;
}
}
}
复制代码
vant
.hairline-common() {
position: absolute;
box-sizing: border-box;
content: ' ';
pointer-events: none;
}
.hairline(@color: @border-color) {
.hairline-common();
top: -50%;
right: -50%;
bottom: -50%;
left: -50%;
border: 0 solid @color;
transform: scale(0.5);
}
复制代码
推荐
更多的细节可以看一下这位大佬的juejin.cn/post/684490…