Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Varying 0-1px gap between div background and border on High-DPI displays

Here is an isolated example of a button I've created in CSS. It features a 1px border with a gradient, and a background gradient. The background gradient is implemented as a pseudo-element to allow its opacity to be faded on hover.

https://codepen.io/anon/pen/wbYoeo?editors=1100

.Button
{
  width: 200px;
  height: 30px;
  cursor: pointer;
    padding: 0.8rem;
    border-style: solid;
    border-image: linear-gradient(
        to right,
        green 0%,
        blue 100%);
    border-image-slice: 1;
    border-width: 1px;
    position: relative;
  margin-top: 10px;
    transition: color 0.2s;
}

.Button::before
{
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    background-image: linear-gradient(
        to right,
        green 0%,
        blue 100%);
    opacity: 0.5;
    transition: opacity 0.2s;
}

The button doesn't render identically between monitors of differing DPI. Screenshots of the button rendered in Chrome on Windows with varying DPI scales:

100% DPI-scaled monitor, rendering correctly with no gap.

150% DPI-scaled monitor, showing a gap between the background and border.

175% DPI-scaled monitor, showing a gap between the background and border.

200% DPI-scaled monitor, rendering correctly with no gap.

I've tried several strategies to render the button, but they all result in the gap:

  • Tried using an image with a gradient instead of linear-gradient on both the border-image and background-image.
  • Tried to use an explicit div for the background gradient instead of a pseudo element.
  • Tried to use an explicit div for the background gradient instead of a pseudo element, and also used a solid left and right border and ::before and ::after pseudo-elements with linear-gradient backgrounds for the top and bottom borders.
like image 529
Drew Gottlieb Avatar asked Oct 15 '22 14:10

Drew Gottlieb


1 Answers

Cause?

I would take an (uneducated) guess that this is caused by sub-pixels when scaling. It can't be a fraction of a pixel, so it chooses a whole pixel value; the parent's calculated value is 1px bigger than the value given to the children at some scales.

Workaround

Take the border off the button div itself, and put it on an ::after pseudo-element so the border and background are both children. The border now appears to scale consistently with the background gradient.

Example

.Button {
  width: 200px;
  height: 30px;
  cursor: pointer;
  padding: 0.8rem;
  position: relative;
  margin-top: 10px;
  transition: color 0.2s;
}

.Button::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  background-image: linear-gradient( to right, green 0%, blue 100%);
  opacity: 0.2;
  transition: opacity 0.2s;
}

.Button:hover::before {
  opacity: 0.5;
}

.Button:active::before {
  opacity: 1;
}

.Button::after {
  content: '';
  border-style: solid;
  border-image: linear-gradient( to right, green 0%, blue 100%);
  border-image-slice: 1;
  border-width: 1px;
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  top: 0;
}

html {
  height: 100%;
  display: table;
  margin: auto;
}

body {
  background: black;
  display: table-cell;
  vertical-align: middle;
  color: white;
  font-family: sans-serif;
}
Click it:
<div class="Button"></div>
like image 137
misterManSam Avatar answered Oct 21 '22 12:10

misterManSam