Home » Html » How to prevent an absolutely positioned element to affect the scrollbar?

How to prevent an absolutely positioned element to affect the scrollbar?

Posted by: admin November 30, 2017 Leave a comment

Questions:

Consider an autosuggest input sitting in a container with some content and a vertical scrollbar:

enter image description here

When autosuggest suggestions are shown, they should appear on top of the content without affecting container’s scrollbar.

I expect to get:

enter image description here

Note that the scrollbar is exactly the same as above.

But, I get the following:

enter image description here

Note that the scrollbar is affected, and suggestions are cut.

Why absolutely positioned suggestions affect container’s scrollbar?

How would you fix that?

Playground here

Notes:

  • When scrolling the container, the input and suggestions should move together.
  • You are allowed to modify the HTML, but the input and the suggestions list should stay inside .autosuggest (consider .autosuggest as a third party component whose HTML cannot be changed, but you can change its CSS).
  • You can use flexbox, if it helps.
  • I’m looking for CSS only solution. No Javascript please.

.container {
  height: 100px;
  width: 300px;
  margin-top: 50px;
  overflow-y: auto;
  border: 1px solid black;
}
.autosuggest {
  position: relative;
  width: 250px;
}
.input {
  font-size: 16px;
  width: 230px;
  padding: 5px 10px;
  border: 0;
  background-color: #FFEBBF;
}
.suggestions {
  list-style-type: none;
  margin: 0;
  padding: 5px 10px;
  width: 230px;
  background-color: #85DDFF;
  position: absolute;
}
.content {
  width: 120px;
  padding: 5px 10px;
}
<div class="container">
  <div class="autosuggest">
    <input class="input" type="text" value="input">
  </div>
  <div class="content">
    content content content content content content content content content content
  </div>
</div>

<div class="container">
  <div class="autosuggest">
    <input class="input" type="text" value="input">
    <ul class="suggestions">
      <li>suggestion 1</li>
      <li>suggestion 2</li>
      <li>suggestion 3</li>
      <li>suggestion 4</li>
      <li>suggestion 5</li>
      <li>suggestion 6</li>
      <li>suggestion 7</li>
      <li>suggestion 8</li>
      <li>suggestion 9</li>
    </ul>
  </div>
  <div class="content">
    content content content content content content content content content content
  </div>
</div>
Answers:

This seems a bit of a janky approach and has a small caveat, but it’s pure CSS/ no HTML structure modifications.

Essentially, I make the .container the main parent instead of trying to work from a lower level (.autosuggest). Step by step:

  • Move position: relative up to .container
  • Make the .autosuggest positioned absolutely (top / left default to 0px).
    • Give it a higher z-index so it’s always on top
  • make .content positioned absolutely all four sides 0px so it’s same size as .container
  • Move the overflow scrollbar to the .content div
  • (here’s the caveat) Set the top padding of .content to the height of .input + the actually desired padding. Otherwise the .content is behind the input element.

And you end up with this:

    .container {
      height: 100px;
      width: 300px;
      margin-top: 50px;
      border: 1px solid black;
      position: relative;
    }
    .autosuggest {
      width: 250px;
      position: absolute;
      z-index: 50;
    }
    .input {
      font-size: 16px;
      width: 230px;
      padding: 5px 10px;
      border: 0;
      background-color: #FFEBBF;
    }
    .autosuggest .input:focus ~ .suggestions{
      display: block;
    }
    .suggestions {
      display: none;
      list-style-type: none;
      margin: 0;
      padding: 5px 10px;
      width: 230px;
      background-color: #85DDFF;
    }
    .content {
      overflow-y: auto;
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      padding: 28px 10px 5px;
    }
<div class="container">
  <div class="autosuggest">
    <input class="input" type="text" value="input">
    <ul class="suggestions">
      <li>suggestion 1</li>
      <li>suggestion 2</li>
      <li>suggestion 3</li>
      <li>suggestion 4</li>
      <li>suggestion 5</li>
      <li>suggestion 6</li>
      <li>suggestion 7</li>
      <li>suggestion 8</li>
      <li>suggestion 9</li>
    </ul>
  </div>
  <div class="content">
    content content<br >content content content content content<br ><br ><br ><br ><br ><br >content content content
  </div>
</div>

I also added some showing/ hiding for the autosuggest box, because it looks nice.

.autosuggest .input:focus ~ .suggestions{
  display: block;
}
.suggestions {
  display: none;
}

Working jsFiddle.

Tested FF 43, Chrome 47


When scrolling the container, the input and suggestions should move together.

This will most likely require JavaScript. By definition, setting the suggestions to absolute position removes them from the rest of the flow of content. You could set a height on the .suggestions div and scroll it separately, but that scrolling can’t (to my knowledge) be tied to the .content scroll if it’s positioned absolutely (again, using CSS alone).

Something like:

$('.content').on('scroll', function () {
  $('.autosuggest .suggestions').scrollTop($(this).scrollTop());
});

Why absolutely positioned suggestions affect container’s scrollbar?

Technically speaking, absolutely positioned elements do not affect the parent’s height. However, they’re still viewed as content of the element – content that overflows (at least in this case).

The overflow property specifies whether to clip content, render scrollbars or just display content when it overflows its block level container. (from MDN)

Since .container has overflow set to scroll, it shows a scroll bar because one of it’s children overflows the bounding box. If you change overflow to hidden it’ll hide all extending content and set to visible you’ll see the .autosuggest extend past the .container, but so does .content (since it’s also content that extends the bounding box).

You see a scrollbar because the .suggest content visually extends beyond the .container block, even though it’s positioned absolutely.

Questions:
Answers:
  • When autosuggest suggestions are shown, they should appear on top of the content without affecting container’s scrollbar.
  • When scrolling the container, the input and suggestions should move together.

This can’t be done with CSS only.

To have suggestions appear on top of the container‘s content, non clipped, it has to have position: absolute and none of its parents (autosuggest and container) can be position: relative.

The down side is that suggestions will not move on scroll.

For suggestions to move with scroll, one of its parents (autosuggest or container) needs to be position: relative.

The down side with that is, and as the container‘s is not overflow: visible, it will be clipped


As already suggested, and assumed the input has to be within the autosuggest element, changing the position: relative on the autosuggest to position: absolute, so the input stays with suggestions on scroll, will likely be the best, though setting z-index on each container will be needed to avoid odd overlapping.

But if the provider of the third party component,… 🙂 …, could be talked into a version where the input could be placed outside the autosuggest element, one could get some more control, using CSS only, of both the suggestions and the content and their layouts, based on if input has focus or not,…

… where this sample maybe could be a good start (click on input to show suggestions).

.container {
  background-color: white;
  width: 300px;
  min-height: 100px;
  margin-top: 50px;
  border: 1px solid black;
  overflow: auto;
  position: relative;
}
.autosuggest {
  position: relative;
  width: 250px;
  z-index: 1;
}
.input {
  font-size: 16px;
  width: 245px;
  padding: 5px 10px;
  border: 0;
  background-color: #FFEBBF;
  position: relative;
  z-index: 1;
}
.suggestions {
  list-style-type: none;
  margin: 0;
  padding: 5px 10px;
  width: 245px;
  background-color: #85DDFF;
  display: none;
}
.content {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  padding: 35px 10px 5px 10px;
  overflow: auto;
  z-index: 0;
}
input:focus ~ .autosuggest .suggestions {
  display: block;
}
<div class="container">
  <input class="input" type="text" value="input">
  <div class="autosuggest">
    <ul class="suggestions">
      <li>suggestion 1</li>
      <li>suggestion 2</li>
      <li>suggestion 3</li>
      <li>suggestion 4</li>
      <li>suggestion 5</li>
      <li>suggestion 6</li>
      <li>suggestion 7</li>
      <li>suggestion 8</li>
      <li>suggestion 9</li>
    </ul>
  </div>
  <div class="content">
    content content content content content content content content content content
    content content content content content content content content content content
    content content content content content content content content content content
    content content content content content content content content content content
    content content content content content content content content content content
    content content content content content content content content content content
  </div>
</div>

<div class="container">
  <input class="input" type="text" value="input">
  <div class="autosuggest">
    <ul class="suggestions">
      <li>suggestion 1</li>
      <li>suggestion 2</li>
      <li>suggestion 3</li>
      <li>suggestion 4</li>
      <li>suggestion 5</li>
    </ul>
  </div>
  <div class="content">
    content content content content content content content content content content
    content content content content content content content content content content
    content content content content content content content content content content
  </div>
</div>

Questions:
Answers:

All you have to do is add z-index properties to .autosuggest and .suggestions here is a code example from codepen

http://codepen.io/HTMLNoob/pen/EPEXKN

.autosuggest {
  z-index: -10;
  width: 250px;
}
.suggestions {
  list-style-type: none;
  margin: 0;
  padding: 5px 10px;
  width: 230px;
  background-color: #85DDFF;
  position: absolute;
  z-index: 10;
}

UPDATE:

I believe this is what you are looking for

http://jsbin.com/fegibaboyo/1/edit?html,css,output

Note: Hover over the input with suggestions.

UPDATE2:

http://jsbin.com/mojopuboku/edit?html,css,output

Here is the new answer.

I have tested this answer throughout most browsers(Firefox, Chrome, Opera) and all of which perform the same results.(I’m not sure about Safari, because I am unable to install Safari on my Windows XP)

Questions:
Answers:
.autosuggest {
  position: fixed; /* add position:fixed */
}

.suggestions {
 /* remove position:absolute */
}

Questions:
Answers:

Why not just pull the ul and li elements out of the div container and then move suggestions up by setting a negative top position?

http://jsbin.com/ficalu/edit?html,css,output

Update:
Change .autosuggest to position: absolute

http://jsbin.com/gezimi/edit?html,css,output

Questions:
Answers:

Easy, remove

position: relative

from class .autosuggest and add to class .container

Questions:
Answers:

Add this to your CSS:

.input:focus + .suggestions {
  position: fixed;
  display: block;
}

And add display: none; to .suggestions in your CSS.

Fiddle here.

Questions:
Answers:

How about this:

  • move autosuggest outside container
  • give the content same margin as height of input (if you’re using LESS/SASS you should be able to compute that)
  • drop the autosuggest down to occupy margin space

My changes in the CSS have a comment next to them. Note that this was only tested in Chrome. You can also probably optimize the HTML a little bit, like removing the container in the example, but you probably have multiple elements in there.

the HTML:

  <div class="autosuggest">
    <input class="input" type="text" value="input">
    <ul class="suggestions">
      <li>suggestion 1</li>
      <li>suggestion 2</li>
      <li>suggestion 3</li>
      <li>suggestion 4</li>
      <li>suggestion 5</li>
      <li>suggestion 6</li>
      <li>suggestion 7</li>
      <li>suggestion 8</li>
      <li>suggestion 9</li>
    </ul>
  </div>

  <div class="container">
    <div class="content">
      content content content content content content content content content content
    </div>
  </div>

the CSS:

.container {
  height: 100px;
  width: 300px;
  overflow-y: auto;
  border: 1px solid black;
}
.autosuggest {
  position: relative;
  top:29px; /* height of input */
  left:1px; /* width of container border */
  width: 250px;
}
.input {
  font-size: 16px;
  width: 230px;
  padding: 5px 10px;
  border: 0;
  background-color: #FFEBBF;
}
.suggestions {
  list-style-type: none;
  margin: 0;
  padding: 5px 10px;
  width: 230px;
  background-color: #85DDFF;
  position: absolute;
}
.content {
  width: 120px;
  margin-top: 29px; /* height of input */
  padding: 5px 10px;
}

Questions:
Answers:

you can make it in “css only” style ONLY if suggestion 1,2,3 elements will be not child of “input” element.

for example:

<input>
 content
 content
</input>

suggestion1
suggestion2
suggestion3

or as working example thar suggest htmlnoob
url in some upper mess

other fix, can work only in old browsers, like ie5.