preface


I found this problem because during the acceptance process, my design partner joked that the border with the same width of 1px displayed different widths on different mobile devices.

After communicating with my design friends zheng and Bian, I got deep in thought and asked the following questions.

The problem

  • The 1px setting is fine, so what causes the inconsistency between devices?
  • Is 1px the same thing that my design buddy and I say?
  • What are the solutions?
  • Can you abstract a good and generic solution?

concept


To answer these questions, the first thing we need is a clear understanding of the concepts involved.

In the CSS specification, units of length can be divided into two categories: absolute and relative. A PX is a relative unit, as opposed to a device pixel.

  • PX(CSS pixels)

Virtual pixels, which can be understood as “intuitive” pixels, are abstract units used by CSS and JS. All lengths in the browser are in CSS pixels, which are in px.

Px is actually the abbreviation of Pixels, which is the basic unit of an image. But as mentioned above, it is an abstract concept. It is not a block or a definite value, but an abstract unit that can adapt to different situations.

Even on the same device, CSS pixels can change. For example, if you open a random web page and hold down command and the + key, you can enlarge the entire page. The browser has not changed, the width has not changed, only the CSS pixel size has changed.

  • DP(device pixels)

Device pixels (physical pixels), as the name implies, the display screen is composed of physical pixels. By controlling the color of each pixel, the screen displays a different image. The physical pixels on the screen are fixed from the day the screen comes out of the factory, and the unit is PT.

Pt is a real absolute unit in the CSS. 1pt is 1/72(inch), and 1 inch is 2.54 cm.

  • DPR(device pixels ratio)

Device pixel ratio (DPR describes the initial ratio between physical pixels and CSS pixels in the unscaled state, as shown in the following figure.

Once you get the device Pixel ratio (DPR), you know the ratio of the device pixels to the CSS pixels.

DPR = Device pixel /CSS pixel

  • DIP(Device independent Pixel)

Device independent pixel/logical pixel

Generally speaking, it can be understood as: device-independent pixels = CSS pixels

So the same DIP is a calculated value that really controls the size of the element.

Here’s an example 🌰 :

  • Requirement: I now have a design at 750px. You need to show a 1px border. The actual development was 375px, and the designers wanted the final result to be 1pt on any screen.

  • DPR=1 means one-fold screen performance:

    Let’s say 1dPI is really showing x Pt’s on a 1 x screen

    1dpi = 1px = x/1

    1DIP just shows 1 PT, in line with the requirements of design students

  • DPR=2, double screen performance:

    Let’s say 1dpi is really showing x Pt on a 2x screen

    1dpi = 1px = x/2, x = 2,

    1DIP displays 2 Pt. Go wrong 😂

  • Same thing with triple screen

    1dip = 3pt

The root cause


After reading the above calculation, you can see why this problem is caused. It is because different devices may have different DPR that the width of the 1px display varies.

Then we can go back and answer the question we raised earlier

  • Q: The 1px setting is fine, so what causes the inconsistency between devices?

    A: Different DPR results in different devices containing different amounts of PT in 1px

  • Q: Is 1px the same thing that my design partner and I say

    A: No, what the designers really want is 1pt

The solution


Once we understand the root cause all that’s left is to deal with him.

Some people say that I will double the width of the screen to 0.5px. But t aside from the fact that splitting 1px into base units is a bit weird, and 0.5px is only supported on ios8+, not android. If you are interested, you can search for this proposal at WWDC 2014. The implementation is also relatively simple.

So there are many ways to avoid the 1px problem throughout history, but there are two types:

  • Other attributes simulate a 0.5px effect
  • Skip the topic of setting 1px

background-image && boder-image


A typical solution to this problem is to skip setting 1px

    .background-image-1px {
      background: url(...) no-repeat left bottom;
      -webkit-background-size: 100% 1px;
      background-size: 100% 1px;
    }
    
    .border-bottom-1px {
      border-width: 0 0 1px 0;
      -webkit-border-image: url(...) 0 0 2 0 stretch;
      border-image: url(...) 0 0 2 0 stretch;
    }
Copy the code

Advantage: At least it solves the problem

Disadvantages: I want to change the color also have to change the picture, and the picture processing round corner will appear blurred problem

border-shadow


Use CSS to simulate border-shadow by 0.5px

.box-shadow-1px {
  box-shadow: inset 0px -1px 1px -1px #c8c7cc;
}
Copy the code

Advantages: Less code, can change colors

Disadvantages: Shadow makes the color lighter, and anyone who looks closely can see that it’s a shadow and not a border…

Pseudo-element + scale scaling


Before you go, I highly recommend it. Go straight to the code. Ant-design-mobile also uses this process

@border-color-base : #EBEDF0; Scale-hairline-common (@color, @top, @right, @bottom, @left) {content: "; position: absolute; background-color: @color; display: block; z-index: 1; top: @top; right: @right; bottom: @bottom; left: @left; Hairline (@direction, @color: @border-color-base) when (@direction = 'top') {border-top: 1PX solid @color; @media (min-resolution: 2dppx) { border-top: none; &::before { .scale-hairline-common(@color, 0, auto, auto, 0); width: 100%; height: 1PX; transform-origin: 50% 50%; The transform: scaleY (0.5); @media (min-resolution: 3dppx) {transform: scaleY(0.33); }}}} // right border.hairline (@direction, @color: @border-color-base) when (@direction = 'right') {border-right: 1PX solid @color; @media (min-resolution: 2dppx) { border-right: none; &::after { .scale-hairline-common(@color, 0, 0, auto, auto); width: 1PX; height: 100%; background: @color; transform-origin: 100% 50%; The transform: scaleX (0.5); @media (min-resolution: 3dppx) {transform: scaleX(0.33); }}}} // 下边框. Hairline (@direction, @color: @border-color-base) when (@direction = 'bottom') {border-bottom: 1PX solid @color; @media (min-resolution: 2dppx) { border-bottom: none; &::after { .scale-hairline-common(@color, auto, auto, 0, 0); width: 100%; height: 1PX; transform-origin: 50% 100%; The transform: scaleY (0.5); @media (min-resolution: 3dppx) {transform: scaleY(0.33); }}}} // left border. Hairline (@direction, @color: @border-color-base) when (@direction = 'left') {border-left: 1PX solid @color; @media (min-resolution: 2dppx) { border-left: none; &::before { .scale-hairline-common(@color, 0, auto, auto, 0); width: 1PX; height: 100%; transform-origin: 100% 50%; The transform: scaleX (0.5); @media (min-resolution: 3dppx) {transform: scaleX(0.33); }}}} // full frame. hairline(@direction, @color: @border-color-base, @radius: 0) when (@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; The transform: scale (0.5); box-sizing: border-box; pointer-events: none; }}}Copy the code

Specific use:

.div1{
    .hairline('top',#eeeeee)
    .hairline('right')
    .hairline('left',#cccccc)
}
.div2{
    .hairline('all',#bbbbbb,3px)
}
Copy the code

Advantages: Use less to interface the CSS, providing flexible functions, reusability, and compatibility. The CSS supports 2-x and 3-x screens through media query. This code is written in a common CSS style.

Disadvantages: There is still room for abstraction, and this scheme uses positon: Absolute for positioning, so the parent element must also satisfy the absolute positioning trigger condition.

At the end

To sum up, the first four questions have been answered. That, too, has been resolved.

Although it seems to be a simple problem, but there are a lot of knowledge points. Sum up the personal gains

  • Screen displays related concepts (PX, DP, DPR, DIP) and their understanding of each other
  • We reviewed the border-image and background-image and border-shadow attributes
  • You have a deeper understanding of the interface design of the CSS.
  • Read more in-depth use of less
  • Obtained the mobile terminal 1PX relatively high quality solution, actually solved the project problem.

supplement

A very good question in the comments. Since there are different numbers of physical pixels in different DPR’s, why isn’t there a problem with numbers greater than 1px?

Here is an explanation from Zhihu, which is very clear.

To put it simply, the screen resolution of mobile phone is getting higher and higher. For a mobile phone of the same size, it has more physical pixels. Because different mobile devices have different pixel densities, we write 1px on different mobile devices equal 1px on this mobile device. Now, when you do mobile development, you usually add a sentence:

<meta name="viewport" content="Width = device - width, initial - scale = 1.0, the maximum - scale = 1.0, user - scalable = no">
Copy the code

This defines the width of the viewport of the page to be the width of the device, the initial and maximum scaling values to be 1, and disallows user scaling. The viewPort Settings are proportional to the physical resolution of the screen, not the same. The window object on the mobile side has a devicePixelRatio property, which represents the ratio of the physical pixels of the device to the CSS pixels. On a Retina iPhone, The value is 2 or 3, and the 1px length written in the CSS is mapped to 2px or 3px on the physical pixel. By setting the viewPort, you can change how many physical pixels are used to render 1px in your CSS. Different viewports are set, and of course 1px lines look different in thickness.