Drew Gottlieb
Drew Gottlieb

Reputation: 73

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:

Upvotes: 7

Views: 4423

Answers (1)

misterManSam
misterManSam

Reputation: 24712

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>

Upvotes: 5

Related Questions