1px Origin of the problem

When working on mobile projects, there is an unavoidable problem: on mobile phones, a 1px thin line looks wider. In fact, this is not only a problem on mobile phones, to be precise, this is a “common problem” in HD screens, in HD PC will have the same.

Hd screens are devices with a high DPR, which refers to physical pixels/CSS pixels, which have a higher density of physical pixels. You can subdivide it into double screen, triple screen.

On a normal screen, one CSS pixel is rendered with only one physical pixel; In a 2x screen, 1 CSS pixel will use 4 physical pixels; In a triple screen, we have nine.

Because only according to this mapping relationship, a picture on different devices, will be the same size.

At this point, I don’t seem to have addressed the question “why 1px lines are wider in HD”.

Substitute the pixel mapping relationship under the HD screen into the 1px line scene, and it will be found that the line width under the 2x screen is 2 physical pixels, and the line width under the 3x screen is 3 pixels.

There is a concept in mathematics that a line has no width and a point has no size. Pixels also have no size.

The density of physical pixels on a 2x screen is twice that of a normal screen. It’s not that each physical pixel is a quarter of the size of a normal screen, it’s that the physical pixels are spaced half the distance of a normal screen.

Using two rows of pixels on a 2x screen will naturally make it look bigger than using one row of pixels on a normal screen.

How to fix 1px problem

To solve the 1px problem, the essence of the hd screen is to display a CSS pixel with a physical pixel.

The simplest and most crude way to do this is to write a 1px border:0.5px in a double screen. This method is only supported on iOS, but android will display it as 0px.

More general scenarios include SVG and pseudo-class elements.

SVG scheme

SVG refers to vector graphics, which, in code, are assembled as XML tags in HTML files.

I created two 100px rectangles with 1 border width using SVG and CSS. In HD, the effect is as follows:

<svg xmlns="custom-namespace" width="100" height="100">
    <rect
        width="100"
        height="100"
        fill="transparent"/ / width1px
        stroke-width="1"
        stroke="black"
    />    
</svg>    
<div     
    style=" width: 100px; height: 100px; // width 1px border: 1px solid black; box-sizing: border-box; "
></div>
Copy the code

Stroke-width, like border-width, sets the rectangle’s edge width to 1px, but the SVG border looks thinner.

The key point is that the viewport size using SVG markup is the same as the rectangle size using RECT markup.

Here is a graphic illustration:

Draw a 100px +1px square using SVG stroke-width.

(Draw a square 100px +1px width with CSS border-width)

A stroke-width line in SVG does not correspond to a border-width line in CSS, but is more like an outline that does not take up space.

Because it doesn’t take up space, it draws a line around the edge of the shape, with one line half the width inside and half the width outside the rectangle. The viewport is exactly the size of the rectangle, so the line is only half as wide.

To support this, you can make the rectangle smaller so that it does not fill the viewport. You can see that it is now as thick as 1px without processing.

The actual operation

That’s the basics of SVG, but actual business code is definitely not directly used in this way, because the code is too scalable.

We usually use the postCSs-write-SVG plug-in, which lets us define SVG directly in a CSS file

// Define an SVG function@svg custom-name {
  width4px;
  height4px;
  @rect {
    fill: transparent;
    width100%;
    height100%;
    stroke-width1;
    stroke: var(--color, black); }}.svg-retina-border {
  border1px solid;  
  border-imagesvg(custom-name param(--color green)) 1 repeat;
}
.normal-border {
  border: px solid green;
}
Copy the code

Pseudoclass element scheme

This scheme uses the pseudo-class element ::before to add a “mask” over the elements that need borders.

.target {
  position: relative;
}
.target::before {
  width200%;
  height200%;
  border1px solid # 333;
  transformscale(0.5);
  content' ';
  position: absolute;
  top0px;
  right0px;
  transform-origin: left top;
  box-sizing: border-box;
  pointer-events: none;
}
Copy the code

Take the double screen as an example, the above is the Demo code, we set the width and height of the mask layer to be 2 times of the target element, the border width is 1px, and then transform it to transform: scale(0.5), the overall width and height is 0.5 times.

After two size transformations, the mask is the same size as the target element, but the border is only 0.5px.

The final effect is as follows:

<div class="retina-border">retina border</div>
<br />
<div class="normal-border">normal border</div>
Copy the code

Which plan to choose

The compatibility and flexibility of the two schemes are compared as follows:

  1. compatibility

The SVG scheme needs to consider the compatibility of border-image, and the pseudo-class element scheme needs to consider the compatibility of Transform. SVG is more compatible.

  1. flexibility

Since SVG can only draw specific shapes, rounded edges are not possible. The pseudo-class element scheme does. Pseudo-class elements are more flexible.

Considering the above considerations, our project chose the pseudo-class element scheme because there are many places to use rounded edges. Besides, the length of the two schemes is not hard to see, and the learning cost of this scheme is also much lower.