Pull up an element in the document flow

Viewed 527

Here's a sample code:

#top {
  background: lightGreen;
}

#content {
  outline: 1px solid red;
}

#bottom {
  background: lightBlue;
}
<div id="top">Top</div>

<div id="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>

<div id="bottom">Bottom</div>

How can I pull up the content div 30% its height in the document flow? As you know, margin-top: -30% doesn't work since the size of the margin as a percentage is relative to the width of the containing block. Positioning and transforming don't seem to help either as they just visually shift the content of the element and its original space is reserved. Any other CSS approach to achieve the following effect?

enter image description here

Note: 30% is just an arbitrary number. It can be any percentage.

8 Answers

How can I pull up the content div 30% its height in the document flow?

One approach to achieve this effect involves three steps:

  1. Calculate the height of the content div using javascript
  2. Use the number calculated by javascript to populate a CSS Custom Property
  3. In your CSS stylesheet, apply the value of the Custom Property to margin-top

Yes, that means you will have to add two lines of javascript - but once you've added those lines, you'll not have to touch javascript again.

Instead, you'll be able to perform, tweak, update all the rest of your manipulations in CSS, using the CSS calc() function.


Working Example

const contentDiv = document.querySelector('#content');
contentDiv.style.setProperty('--contentHeight', (0 - contentDiv.offsetHeight + 'px'));
body {
  padding-top: 40px; /* <= Not necessary; just for breathing space */
}

#top {
  background: lightGreen;
}

#content {
  margin-top: calc(var(--contentHeight) / 100 * 30); /* 30% of #content height */
  outline: 1px solid red;
}

#bottom {
  background: lightBlue;
}
<div id="top">Top</div>

<div id="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>

<div id="bottom">Bottom</div>


Further Reading:

Margin percentages are based on " logical width of containing block".

logical width A measurement in the inline dimension: refers to the physical width (horizontal dimension) in horizontal writing modes, and to the physical height (vertical dimension) in vertical writing modes. ref

So if we change containing block to vertical writing mode then margins will be based on height. And we can use margin-top: -30% based on height of the container.

Demo:

body {
  margin: 0;
  padding: 0;
  /*adding the top padding to able to see shifting parts*/
  padding-top: 100px;
  /* for better demo limiting to stackoverflow result width */
  width: 590px;
}

#top {
  background: lightGreen;
}

#wrapper {
  /* making it vertical mode */
  writing-mode: vertical-lr;
  width: 100%;
}

#content {
  border: 1px solid red;
  /* back to horizontal mode */
  writing-mode: horizontal-tb;
  float: left;

  /*  margin-top: -30%; */
  /* to see content movement live */
  animation: moveMe 4s steps(1, start) infinite alternate none;
}

#bottom {
  background: lightBlue;
  /* back to horizontal mode */
  writing-mode: horizontal-tb;
  width: 100%;
  float: left;
}


/* changing margin-top in steps*/


/* Note: we've used percentages here. You can do it manually. */

@keyframes moveMe {
  0% {
    margin-top: -0%;
  }
  10% {
    margin-top: -0%;
  }
  40% {
    margin-top: -30%;
  }
  70% {
    margin-top: -60%;
  }
  100% {
    margin-top: -100%;
  }
}
<div id="top">Top</div>
<div id="wrapper">
  <div id="content">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
    in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. lorem100

  </div>
  <div id="bottom">Bottom</div>
</div>

You can add more text to the #content div without modifying the CSS.


Practical use of this technique could be https://stackoverflow.com/a/70067583/15273968

As I understand, the right question should be "pull up an element and all the elements below it". If that is true, there are 2 scenarios.

Scenarios 1: Your #top element's height is smaller than 30% of your #content element's height. In this situation, we have a pure CSS solution. But you need to adjust your HTML a bit.

  • Put your #top div into #content div
  • Set position: absolute and top: 30% to #top div
  • Set transform: translateY(-100%) for #top div. 100% value here will apply based on the height of #top so it will be placed in deserved position. It's simply math.

#top {
  background: lightGreen;
  position: absolute;
  top:30%;
  width: 100%;
  transform: translateY(-100%);
}

#content {
  position: relative;
  outline: 1px solid red;
}

span {
  position: relative;
  z-index: 1;
}

#bottom {
  background: lightBlue;
}
<div id="content">
<div id="top">Top</div>
<span>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</span>
</div>

<div id="bottom">Bottom</div>

Scenarios 2: Your #top element's height is bigger than 30% of your #content element's height. In this situation, I'm afraid that JS is the only way can help you. You can calculate the height of the element by JS and add a negative margin to it based on the height

document.addEventListener("DOMContentLoaded", ()=>{
  const content = document.getElementById('content')
  const height = content.getBoundingClientRect().height
  content.style.marginTop = `-${height*0.3}px`
});
#top {
  background: lightGreen;
}

#content {
  outline: 1px solid red;
}

#bottom {
  background: lightBlue;
}
<div id="top">
Top <br/>
Top <br/>
Top <br/>
Top <br/>
Top <br/>
Top <br/>
</div>

<div id="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>

<div id="bottom">Bottom</div>

Here is a less-than-elegant solution using position: relative on the body (or insert your own container div, if you like) and position: relative plus top on the content div to move it up a percentage.

When the parent is body, the percentage is necessarily of the body, which typically is the whole viewport. If you want to use bigger percentages and not have the content shoot off-screen, you could make body smaller, if nothing else is on the page, or use a wrapper div.

Now, I know you said:

Positioning and transforming don't seem to help either as they just visually shift the content of the element and its original space is preserved.

But per your comment that "any CSS goes", I've just applied the same CSS to both the #content and the #bottom divs to avoid the appearance of reserved space:

html, body {
    height: 100%; /* an absolute or relative value here, or an absolute value lower down, is required so that percentage value of the relevant elements has something to be a percent _of_ */
    margin: 0; /* just for demo's sake so there is no scrollbar */
}
#top {
    background: lightGreen;
}
#content {
    outline: 1px solid red;
    position: relative; /* required to allow positioned property of "top" to work */
    top: -1.7%; /* does the actual work of moving the element up */
}
#bottom {
    background: lightBlue;
    position: relative; /* required to allow positioned property of "top" to work */
    top: -1.7%; /* does the actual work of moving the element up */
}
<div id="top">Top</div>

<div id="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>

<div id="bottom">Bottom</div>

The result that I am seeing in the question is when the content of the page is pulled up the header should stay behind. This will automatically be adjusted with the viewport when the content has a scrollable body.

Note that the attribute top in element Id #top can be changed to use percentages as well.

<style>
#top {
    background: lightGreen;
    top: 0px;
    position: sticky;
    z-index: 0;
}

#content {
    outline: 1px solid red;
    z-index: 1;
    background-color: white;
}

#bottom {
    background: lightBlue;
}
</style>


<div id="top">Top</div>

<div id="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>
<div id="bottom">Bottom</div>

Preliminary idea

If it is permissible to duplicate content in two blocks (for example, if the page code is compiled by the CMS engine), then we can create a system of blocks.

The first instance of content will be hidden using the visibility property, but will set the 100% reference height we need. The second instance of the block will be visible and positioned absolutely relative to its invisible counterpart.

The content has risen to the desired percentage of height, and this percentage can be changed arbitrarily. The top property in percentages means the percentages of the height of the containing block.

But we got a gap between the raised block and the footer of the page:

#top {
  background: lightGreen;
}

#bottom {
  background: lightBlue;
}

.content {
  outline: 1px solid red;
}

.position-relative {
  position: relative;
}

.pull-up {
  position: absolute;
  top: -30%;
  left: 0;
  right: 0;
}

.visibility-hidden {
  visibility: hidden;
}
<div id="top">Top</div>

<div class="position-relative">
  <div class="content visibility-hidden">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>

  <div class="content pull-up">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>
</div>

<div id="bottom">Bottom</div>

Final Solution Proposal

To get rid of the gap, we need to pull up all subsequent page content as well. To do this, we put it, along with the content duplicate, in the common intermediate wrapper pull-up.

It is this wrapper that we position absolutely and raise it 30% of the parent's height. Its parent is the position-relative block with the CSS property of the same name. And the parent's height is set by the first invisible instance of our content.

To make sure that the absolutely positioned box is fully visible in the browser, I enlarged the bottom box.

#top {
  background: lightGreen;
}

#bottom {
  background: lightBlue;
}

.content {
  outline: 1px solid red;
}

.position-relative {
  position: relative;
}

.pull-up {
  position: absolute;
  top: -30%;
  left: 0;
  right: 0;
}

.visibility-hidden {
  visibility: hidden;
}
<div id="top">Top</div>

<div class="position-relative">
  <div class="content visibility-hidden">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
    in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>

  <div class="pull-up">
    <div class="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
      dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>

    <div id="bottom">
      <p>Bottom 1</p>
      <p>Bottom 2</p>
      <p>Bottom 3</p>
      <p>Bottom 4</p>
      <p>Bottom 5</p>
      <p>Bottom 6</p>
      <p>Bottom 7</p>
      <p>Bottom 8</p>
      <p>Bottom 9</p>
    </div>
  </div>
</div>

If you know content height then you can use sibling combinator ~ to pull all following siblings up.

:root {
  --content-height: 100px;
}

body {
  padding-top: 100px;
  width: 590px;
}

#top {
  background: lightGreen;
}

#content {
  border: 1px solid red;
  height: var(--content-height);
}

#bottom {
  background: lightBlue;
}

#content,
#content~div {
  /*
  transform: translateY(calc(var(--content-height) * -0.3));
  */
  animation: moveMe 4s steps(1, start) infinite 1s alternate none;
}

@keyframes moveMe {
  0% {
    transform: translateY(0px);
  }
  10% {
    transform: translateY(0px);
  }
  40% {
    transform: translateY(calc(var(--content-height) * -0.3));
  }
  70% {
    transform: translateY(calc(var(--content-height) * -0.6));
  }
  100% {
    transform: translateY(calc(var(--content-height) * -1));
  }
}
<div id="top">Top</div>
<div id="content">
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
  in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>
<div id="bottom">Bottom</div>

Using same strategy you can position all of them relative, absolute, or fixed and set top:30%, transform:translate(-100%).


Group: If you group #content and #bottom then you need to move only the group:

:root {
  --bottom-height: 1.2em;
}

body {
  padding-top: 110px;
  width: 590px;
}

#top {
  background: lightGreen;
}

#group {
  /*transform: translateY(-30%)*/
  animation: moveMe 4s ease-in-out infinite 1s alternate none;
}

#content {
  outline: 1px solid red;
}

#bottom {
  background: lightBlue;
}

@keyframes moveMe {
  0% {
    transform: translateY(0px);
  }
  100% {
    transform: translateY(calc(-100% + var(--bottom-height)));
  }
}
<div class=wrapper>
  <div id="top">Top</div>
  <div id="group">
    <div id="content">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
      in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    </div>
    <div id="bottom">Bottom</div>
  </div>
</div>

Hope it's work for you !!!

.wrapper { display: flex; flex-direction: column;}
        #top {
          height: 1em;
          background: lightGreen;
          order: 2;
        }

        #content {
          outline: 1px solid red;
          order: 1;
          margin-bottom: -15px;
          position: relative;
        }

        #bottom {
          height: 1em;
          background: lightBlue;
          order: 3;
        }
<div class="wrapper">
        <div id="top"></div>
        <div id="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>
        <div id="bottom"></div>
    </div>

Related