Super long lines

I’m developing a website for a client, the design calls for some long vertical lines that sort of “connect” various elements (although they aren’t attached).
I’m trying to think of the best way of doing this.

On the surface level something like this would work:

.line {
	height: 2604px;
	border: 0.5px solid var(--white);
	position: absolute;
	left: 28px;
	top: 0px;
}

But it’s not responsive at all. I need something that will line up with a title at at the top of the page, and stay lined up with a div much further down the page.

Any ideas on this?

Thoughts on using a background SVG image of a straight line? :grinning:

With CSS, you can center it with background-position and have it repeat it only vertically with background-repeat.

1 Like

I did some stuff a while ago which calculated the grid-template-row height and made some connecting lines (diagonal). It was a total mind screw. :grin:

The problem with that approach is that the items must stay in the grid-row and not be auto-rows which can be a problem for responsiveness.

I nearly got sick of it and explicitly made connecting lines with js and if I were to do it again I would do exactly that.

If you explicitly want to connect a strictly vertical line between elements you can do it like this:

CSS:

  • needs position fixed for the line
  • needs lower z index than elements in front
*{ z-index :1;}

.hiddenLine{
            opacity: 0;
            position: fixed;
            width:  .25rem;
            background-color: red;
        }

        .line{
            z-index: 0;
            opacity: 1;
            transition: opacity 1s;
        }

JS:

  • builds a line between the LHS of 2 elements
  • can pass in a vertical margin (space between the line and the elements)
  • can pass in a left margin (push in line from LHS edge of bottom element)
const makeLine = function(parent_id, line_id, start_element, end_element, vert_margin = 0, left_margin = 0){
    let start_rect = document.getElementById(start_element).getBoundingClientRect()
    let end_rect = document.getElementById(end_element).getBoundingClientRect()
    let parent = document.getElementById(parent_id)
    let line = document.getElementById(line_id)
    let height = end_rect.top - start_rect.bottom - vert_margin;
    let pos_left = end_rect.left + left_margin;
    line.classList.add('line')
    line.zIndex = -1;
    line.style.height = `${height}px`;
    line.style.left = `${pos_left}px`;
    line.style.top = `${start_rect.bottom + (vert_margin / 2)}px`
}

Basic Demo:

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Document</title>

    <style>

        *{

            z-index: 1;

        }

        #container{

            display: grid;

            width: 100%;

            height: 350px;

            grid-template-columns: [full-start] 1fr 1fr [full-end];

            grid-template-rows: 100px 1fr 100px;

            grid-gap: 20px;

        }

        .purple {

        grid-row: span 2;

        }

        .blue {

        grid-column: span 2;

        }

        .hiddenLine{

            opacity: 0;

            position: fixed;

            width:  5px;

            background-color: red;

        }

        .line{

            z-index: 2;

            opacity: 1;

            transition: opacity 1s;

        }

    </style>

<script>

const makeLine = function(parent_id, line_id, start_element, end_element, vert_margin = 0, left_margin = 0){

    let start_rect = document.getElementById(start_element).getBoundingClientRect()

    let end_rect = document.getElementById(end_element).getBoundingClientRect()

    let parent = document.getElementById(parent_id)

    let line = document.getElementById(line_id)

    let height = end_rect.top - start_rect.bottom - vert_margin;

    let pos_left = end_rect.left + left_margin;

    line.classList.add('line')

    line.zIndex = -1;

    line.style.height = `${height}px`;

    line.style.left = `${pos_left}px`;

    line.style.top = `${start_rect.bottom + (vert_margin / 2)}px`

}

const run = () => {makeLine('container', 'line', 'start', 'end', 6,0)}

 

document.addEventListener("DOMContentLoaded", run)

</script>

</head>

<body>

<div id="container">

   

  <div id = 'start' class="item grey" style="background-color: grey;"></div>

  <div class="item purple"style="background-color: purple;"></div>  

  <div class="item orange"style="background-color: orange;"></div>

  <div id = 'end' class="item blue"style="background-color: blue;"></div>

<div id = 'line' class="hiddenLine"></div>

</div>  

</body>

</html>
1 Like

I like the simplicity of it, but I’m not sure how it would be responsive. For example if I had markup like:

<div class="container">
   <div class="first-box"> Variable content length</div>
   <div class="other-content"></div>
   <p> Some other elements for good measure</p>
   <div class="second-box">More content</div>
</div>

If I wanted to have the SVG go from .first-box to .second-box. How would it know the correct length if we were on mobile where the page might be 300% vs a desktop where the page would only be 100% or 120%?

Thanks Steve, I think this approach, with a few tweaks, is what I will stick with for now.

Would the container element act as a bounding box between the first and second boxes? In other words, if the SVG line was centered inside container, would that give you the right result where it would go from the top of the container to the bottom of the container?

Yeah @kirupa is right,

You can do it with the background (absolute) if:

  • Both boxes have the same parent (parent expands to content) AND the boxes are first and last child
  • OR there is one element between the 2 boxes (put image as child of the element between and use position: absolute)
  • OR all the elements between the boxes have the same height that can be calculated

This is why we need display: sub-grid

You could put all the elements between the boxes in a wrapper BUT still have them use the wrapper’s parent auto-flow and use the wrapper for the size and position of the line.

display: contents doesn’t work because it removes the box entirely from the wrapper unfortunately…

For the first time in a while its Chrome that is late to the party on a critical feature.
Even Safari is doing it…

1 Like

Thanks @steve and @kirupa, I’m a bit tardy in the response. I like @kirupa’s solution it’s elegant but the layout of the page is a variable number sections and divs. Something like:

<div class="container">
     <section class="subsection">
         <div class="content">
            <!-- This is the start of the line -->
            <div class="line"></div>
            <span> Some amount of content / images... etc </span>
        </div>
    </section>
    <section class="subsection">Another Section in a variable number of sections</section>
    <section class="subsection">
        <div class="content">
             <h2>Hi</h2>
            <div class="box">
                The top of this box may be where the line should connect too?
           </div>
        </div>
    </section>
</div>

From what I can tell I’d have to use @steve.mills 's JS approach here.

2 Likes