3

I am backend dev, trying to get better at frontend things. I am wondering if there is any way, if you have a group of divs next to each other to draw a border around them when they do not represent a square shape. Here an example of what I am trying:

#child1 {
  top: 0px; 
  left: 0px;
  background:red;
}
#child2 {
  top: 50px; 
  left: 0px;
  background:blue;
}
#child3 {
  top: 0px; 
  left: 50px;
  background:green;
}
.child {
  position: absolute; 
  width: 48px;
  height: 48px;
  border: 2px black solid;
}
<div>
  <div style="position: relative" class="parent">
    <div id="child1" class="child">1</div>
    <div id="child2" class="child">2</div>
    <div id="child3" class="child">3</div>
  </div>
</div>

https://jsfiddle.net/udxes0z3/

I would like to have the borders you can see but not the borders between "1" + "3" and "1" + "2". Is there any way this can done via CSS? I would like to avoid calculating those borders programmatically (that is what I am currently doing, which is causing performance issues).

Thx for any hints or telling me this is not possible ;-)

*edit

The example is over simplified compared to the "real" problem, actuals shapes may look like this:

Actual shapes example

  • 1
    Like this -> https://jsfiddle.net/em6950y8/? – Mr T Feb 19 '21 at 08:32
  • Yes exactly. What you did there is close to what I am currently doing. I am wondering if there is some "css magic" achieving the same. – user3909264 Feb 19 '21 at 08:40
  • Are you looking for a dynamic way of drawing borders? – Mr T Feb 19 '21 at 08:42
  • Any way for achiving this without setting borders individually. In my actual usecases there are several thousand cells, each one calculating its borders individually is rather slow (as this calculation logic needs to be responsive, you can drag and drop cells). – user3909264 Feb 19 '21 at 08:51
  • Did you try looking at `:nth-child()` in CSS where you can define `odd`, `even` and also pick the nth elements. – Ismail Vittal Feb 19 '21 at 09:10
  • _“I would like to avoid calculating those borders programmatically (that is what I am currently doing, which is causing performance issues)”_ - what about the positioning of those child elements, did you not have to calculate that as well to begin with here? Is this the actual use case, or did you just use absolute positioning for demonstration purposes here? – CBroe Feb 19 '21 at 09:57
  • I'm not clear how general you want this solution to be. Is it for any number of elements, positioned 2 to a line, or n to a line depending on parent width and so on? – A Haworth Feb 19 '21 at 10:18
  • The best way to describe what i am doing might be a tetris game. We have shapes consisting of n squares. The shapes are not fixed, so any shape can be possible. Each object (consisting of 3 childs in upper example) is positioned absolute within a grid. Each object builds itself by positioning its cell absolute (within the wrapping object), thus the absolute positioning. Sorry if this sounds confusing, i am not a native speaker. – user3909264 Feb 19 '21 at 11:58
  • Then there is no easy, CSS-only solution for this. How hard a solution that “calculates” this has to hit in terms of performance, depends on what you are actually doing, I suppose. We’d need more info on that, if you want possible suggestions for improvement. And also, is this a one-time thing, or do you need to periodically update this, because the squares move/change position, or something? – CBroe Feb 19 '21 at 12:03
  • Thanks for adding the more complex items, I can see a bit better what is needed. I think we can do it with just CSS and that my answer with pseudo elements is generalisable to them. I assume now that the shapes are rectangles but all the same size, is this true? I'll try a general pattern. – A Haworth Feb 19 '21 at 13:22
  • No size also differs, they may consist of only one cell or multiple hundreds. And yes they might need to be update, as you are able to remove cells or add cells to one item. – user3909264 Feb 19 '21 at 14:29

3 Answers3

3

This is possible by introducing a pseudo element on each square.

We draw the initial setup but with each square having twice the desired end-result border width. Then overlay each square with a square of the same color, with no border, slightly bigger and higher z index.

This then covers half the border round itself. And as the squares' borders overlap each other these overlaid squares between them overlay the inner borders and leave half the width of the outer border (i.e. you get the desired outer width).

There is a wrinkle. The content of each square gets overlaid as well. For this test I've put the content into the content of the pseudo element. But this may not be suitable for an entirely general situation and we'd have to look at putting an extra element inside each square with its content and showing that above the pseudo element.

Assuming this is the desire outcome for now: enter image description here

Here is the snippet. Note, dimensions and colors have been put into CSS variables to make it easier to try different layouts.

* {
  margin: 0;
  padding: 0;
}

#child1 {
 top: 0px; 
 left: 0px;
 --bg: red;
}

#child2 {
 top:  var(--w);
 --bg: blue;
}

#child3 {
 top: 0px; 
 left:  var(--w);
 --bg: green;
}
.child {
 --w: 50px;/* width and height of each square */
 --b: 2px;/* width of border */
 position: absolute;
 background-color: var(--bg);
 width: calc(var(--w) - (2 * var(--b)));
 height: calc(var(--w) - (2 * var(--b)));
 border: calc(2 * var(--b)) black solid;
}

.child::after {
  content: '1';
  position: absolute;
  background-color: var(--bg);
  top: calc(var(--b) * -1);
  left: calc(var(--b) * -1);
  width: calc(var(--w) - var(--b));
  height: calc(var(--w) - var(--b));
  opacity: 1;
  border: 1px transparent solid;
  z-index: 9999;
}

#child1::after {
  content: '1';
}

#child2::after {
  content: '2';
}

#child3::after {
  content: '3';
}
<div>
 <div style="position: relative" class="parent">
   <div id="child1" class="child"></div>
   <div id="child2" class="child"></div>
   <div id="child3" class="child"></div>
 </div>
</div>
A Haworth
  • 30,908
  • 4
  • 11
  • 14
  • This was exactly what I was looking for, it is able to handle my complex cases. Thx a lot! – user3909264 Feb 19 '21 at 13:39
  • Great, it was fun to do. If you want stuff to be in each div rather than in the 'content' you could change the solid pseudo divs to just have border of their particular color and leave the middle parts blank. – A Haworth Feb 19 '21 at 13:53
0

Just remove the borders where you want that to occur.

.child {
  position: absolute;
  width: 48px;
  height: 48px;
  border: 2px black solid;
}

#child1 {
  top: 0px;
  left: 0px;
  background: red;
  border-right-style: none;
  border-bottom-style: none;
}

#child2 {
  top: 50px;
  left: 0px;
  background: blue;
  border-top-style: none;
}

#child3 {
  top: 0px;
  left: 50px;
  background: green;
  border-left-style: none;
}
<div>
  <div style="position: relative" class="parent">
    <div id="child1" class="child">1</div>
    <div id="child2" class="child">2</div>
    <div id="child3" class="child">3</div>
  </div>
</div>

Note: on high def screens, e.g. retina, where several screen pixels make up one CSS pixel you may see a faint line between some squares. I think this depends on whether the calculation results in part-CSS pixels. We may need to expand each overlaid square by a pixel to get over this, but I haven't experimented as yet.


Now of course the challenge is to not have this so simplified. Thus we might slide them under the sibling but not have borders on the "first" sibling (red). Since you are positioning them, this sort of works.

.child {
  position: absolute;
  width: 48px;
  height: 48px;
  border: 2px black solid;
}

#child1 {
  top: 0px;
  left: 0px;
  background: red;
  border-right-style: none;
  border-bottom-style: none;
  z-index: 1;
}

#child2 {
  top: 50px;
  top: 48px;
  left: 0px;
  background: blue;
  z-index: -10;
}

#child3 {
  top: 0px;
  left: 50px;
  left: 48px;
  background: green;
  z-index: -10;
}
<div>
  <div style="position: relative" class="parent">
    <div id="child1" class="child">1</div>
    <div id="child2" class="child">2</div>
    <div id="child3" class="child">3</div>
  </div>
</div>

Since you are positioning them, this sort of works if you use flex.

I don't have time to fully vet that but perhaps some use as in this question which is slightly related to that concept How to create “collapsed” borders around flex items and their container?

Mark Schultheiss
  • 32,614
  • 12
  • 69
  • 100
-1

Here is my attempt, although it is not completely dynamic as it will rely on additional css class against the parent element:

.parent {
  border: 2px black solid;
  display: grid;
  grid-template-columns: 1fr 1fr;
}

.parent.odd:after {
  content: '';
  border-top: 2px black solid;
  border-left: 2px black solid;
  box-shadow: 2px 2px 0 0 white; // box-shadow covers parent's border
}

.child {
  height: 48px;
}

#child1 {
  background:red;
}

#child2 {
  background:blue;
}

#child3 {
  background:green;
}
<div>
<!-- add "odd" css class from code behind -->
<div style="position: relative" class="parent odd">
<div id="child1" class="child">1</div>
<div id="child2" class="child">2</div>
<div id="child3" class="child">3</div>
</div>
</div>

I am also using css grid in this example, which may not be the right solution depending on browser support needed - https://caniuse.com/?search=grid

Mr T
  • 839
  • 6
  • 16
  • This css grid is really interessting, I will have a look. But for my usecase I think this will not work (see edit in first post). – user3909264 Feb 19 '21 at 12:03