SharRoynor
SharRoynor

Reputation: 21

CSS border greater than it should be

console.log('hello!')
.item {
  display: flex;
  flex-wrap: wrap;
}

.items {
  border: 1px solid red;
  width: 200px;
  padding: 10px;
}
<body>
  <h1>Hello there!</h1>

  <div class="item">
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
  </div>
</body>

Here I need to set red border for my cards but for this borders width is 2px but I need 1 px for all borders. How can I fix it?

Upvotes: 2

Views: 226

Answers (2)

David Thomas
David Thomas

Reputation: 253506

There are a number of problems with the obvious attempts; here I enforce a gap on the elements in the layout, in order to take the first, most-obvious approach, that of separating the adjacent borders. This prevents those elements from being placed immediately beside one another, so avoiding the the problem of the visual 'double-width' border effect:

*,
 ::before,
 ::after {
  box-sizing: border-box;
  font: 1rem / 1.5 Roboto, Montserrat, sans-serif;
  margin: 0;
  padding: 0;
}

section {
  width: clamp(50em, 70vw, 1200px);
  margin-block: 2em;
  margin-inline: auto;
}

.item {
  display: flex;
  flex-wrap: wrap;
  margin-block: 1em;
}

.item.withGap {
  gap: 1em;
}

.items {
  border: 1px solid red;
  width: 200px;
  padding: 10px;
}

.item.noGap .items+.items {
  border-inline-start-width: 0;
}

.setColumns {
  --columns: 5;
  --gap: 1em;
  --flexBasis: calc((100%/var(--columns) - var(--gap)));
  gap: var(--gap);
}

.setColumns .items {
  flex-basis: var(--flexBasis);
}
<section>
  <div class="item withGap">
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
  </div>
</section>

This, of course, is reduces the the way in which you can lay out your elements; to later reduce, or remove, the gap means you have to reconsider the border of the elements being laid out.

The other obvious option is to remove borders on adjacent items:

*,
 ::before,
 ::after {
  box-sizing: border-box;
  font: 1rem / 1.5 Roboto, Montserrat, sans-serif;
  margin: 0;
  padding: 0;
}

section {
  width: clamp(50em, 70vw, 1200px);
  margin-block: 2em;
  margin-inline: auto;
}

.item {
  display: flex;
  flex-wrap: wrap;
  margin-block: 1em;
}

.item.withGap {
  gap: 1em;
}

.items {
  border: 1px solid red;
  width: 200px;
  padding: 10px;
}

.item.noGap .items+.items {
  border-inline-start-width: 0;
}

.setColumns {
  --columns: 5;
  --gap: 1em;
  --flexBasis: calc((100%/var(--columns) - var(--gap)));
  gap: var(--gap);
}

.setColumns .items {
  flex-basis: var(--flexBasis);
}
<section>
  <div class="item noGap">
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
  </div>
</section>

This can work quite well for the first row of the layout, but if – or when – your layout runs to a second row it means that the first element of the new row is lacking its border-inline-start, and all elements of the second row retain their border-block-start because there's no way (as of writing) to select elements according to the row in which they're laid out, unfortunately.

Another obvious – and somewhat complicated – way is to use JavaScript, of course:

;
const getRows = (haystack, n) => {
    let temp = [...haystack],
      result = [];

    if (n === Infinity) {
      return haystack;
    }

    while (temp.length > 0) {
      result.push(
        temp.splice(0, n)
      );
    }

    return result;
  },
  unitsInPixels = (val) => {
    let cssVals = cssValue(val),
      {
        number,
        units
      } = cssVals,
      div = document.createElement('div');
    div.style.width = `1${units}`;
    div.style.height = `1${units}`;
    div.style.margin = 0;
    div.style.padding = 0;
    div.style.boxSizing = 'border-box';
    div.style.position = 'absolute';
    div.style.left = '1000%'
    document.body.appendChild(div);
    let measurements = div.getBoundingClientRect(),
      {
        width,
        height
      } = measurements;

    div.remove();

    return Math.floor(number * Math.min(width, height));
  },
  cssValue = (value, opts = {}) => {
    let settings = {
      toInt: false,
      toSensibleMinimum: false,
    };
    Object.keys(opts).forEach(
      (key) => settings[key] = opts[key]
    );
    let n = settings.toInt === true ? parseInt(value, 10) : parseFloat(value),
      unit = value.replace(/^(\d+)/, '');
    n = settings.toSensibleMinimum === true && n === 0 ? ++n : n;
    return {
      originalValue: value,
      number: n,
      units: unit,
      numberWithUnits: `${n + (unit ? unit : '')}`,
    }
  },
  styleCellsOf = (targets) => {

    if (!targets) {
      return false;
    }

    targets.forEach(
      (el) => {
        const children = el.children,
          details = window.getComputedStyle(el, null),
          columns = cssValue(details.getPropertyValue('--columns'), {
            toSensibleMinimum: true
          }).number,
          gap = cssValue(details.getPropertyValue('--gap')),
          matrix = getRows(children, columns);

        if (unitsInPixels(gap.numberWithUnits) > 0) {
          return false;
        }

        matrix.forEach(
          (rowN, rowCounter) => {
            rowN.forEach(
              (cell, colCounter) => {
                if (rowCounter > 0) {
                  cell.style.borderBlockStartWidth = 0;
                }
                if (colCounter > 0) {
                  cell.style.borderInlineStartWidth = 0;
                }
              });
          });
      });

  };


styleCellsOf([...document.querySelectorAll('.setColumns')]);
window.addEventListener('resize', (e) => {
  styleCellsOf([...document.querySelectorAll('.setColumns')])
});
*,
 ::before,
 ::after {
  box-sizing: border-box;
  font: 1rem / 1.5 Roboto, Montserrat, sans-serif;
  margin: 0;
  padding: 0;
}

section {
  width: clamp(50em, 70vw, 1200px);
  margin-block: 2em;
  margin-inline: auto;
}

.item {
  display: flex;
  flex-wrap: wrap;
  margin-block: 1em;
}

.item.withGap {
  gap: 1em;
}

.items {
  border: 1px solid red;
  width: 200px;
  padding: 10px;
}

.item.noGap .items+.items {
  border-inline-start-width: 0;
}

.setColumns {
  --columns: 5;
  --gap: 1em;
  --flexBasis: calc((100%/var(--columns) - var(--gap)));
  gap: var(--gap);
}

.setColumns .items {
  flex-basis: var(--flexBasis);
}
<section>
  <div class="item setColumns" style="--columns: 4; --gap: 1em">
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
  </div>
  <div class="item setColumns" style="--columns: 5; --gap: 0.05em">
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
  </div>
  <div class="item setColumns" style="--columns: 3; --gap: 0.03em">
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
  </div>
  <div class="item setColumns" style="--columns: 3; --gap: 0em">
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
    <div class="items">
      <h4>Card 1</h4>
      <span>Card content</span>
    </div>
  </div>
</section>

But this is potentially expensive, and needs to be explicitly connected to the HTML elements. This also comes with caveats, if you look at the second JavaScript flex-layout (in the demo just above), you may notice a tiny break in the apparently-flowing borders on the block-axis due to the way in which browsers, and monitors, handle sub-pixel rendering. So this approach is, also, imperfect although it does let you use CSS custom properties to style the elements.

It also reduces the purpose of using flex-layout, since it requires an explicitly-set number of columns (this could, of course, be worked around but to do so would seem to make the semi-simple JavaScript rather more complex, unfortunately).

So, again, while it mostly works in the event of a --gap custom property being set to 0, small deviations between, as an example (in my case) between 0em and 0.03em leads to either apparent-doubling of adjacent borders, or slight gaps between borders.

The second and third show gaps of 0.05em and 0.03em; each of which – in my case – seem to show the small notches/gaps in the borders.

Unfortunately, the only solutions are non-ideal.

Upvotes: 0

nicael
nicael

Reputation: 19049

Neighboring borders are the core problem here. There're different ways to fix it, one of them could involve forcibly removing (de-duplicating) borders that appear close to each other. However, I don't think it's a good option, especially since it doesn't allow free flow of your cards (in case of regrouping - due to window width changes).

You could consider adding margins between cards, so as the cards would look cleaner.

.item {
  display: flex;
  flex-wrap: wrap;
}

.items {
  border: 1px solid red;
  width: 200px;
  padding: 10px;
  margin: 5px;
}

The same look can be achieved when grid-gap:10px used.

.item {
  display: flex;
  flex-wrap: wrap;
  grid-gap:10px;
}

.items {
  border: 1px solid red;
  width: 200px;
  padding: 10px;
}

Upvotes: 1

Related Questions