1

Assume you have a website with a position: fixed header. If we click internal (same-page) links, that header will overlap the content we are taken to via the link.

I created a solution to this problem, using a pseudo-element with negative margin, which takes advantage of parent-child margin collapsing to prevent header overlap from occurring.

In short summary, the pseudo-element's top margin collapses with main element's top margin, causing the pseudo-element to stay within main, but push main's content down while at the same time pull content above it downwards.

It works well, except main's background will paint on top of background of element above it.

That can probably be prevented with position: relative and z-index on all elements.

My question: Is there a better way? Also, is this the typical way this problem is solved?

A minimal working example can be found below.

Note: The pseudo-element has background-color set on it, just to illustrate its presence. That background should be removed when testing it.

body {
  height: 300vh;
  text-align: center;
  width: 100%;
}

.header {
  height: 40px;
  width: 100%;
  background-color: yellow;
  position: fixed;
  top: 0;
}

.foo {
  height: 50px;
  background-color: grey;
}

.foo:nth-child(2) {
  margin-top: 40px;
  background-color: red;
}

.main {
  height: 100px;
  background: blue;
}

.main div {
  height: 100px;
}

.main::before {
  content: "pseudo-element (when removing its background-color, you see how .main unfortunately paints on top of foo)";
  display: block;
  background: green;
  height: 40px;
  margin-top: -40px;
}

.main2 {
  height: 100px;
  background-color: blue;
}
<div class="header">
  <a href="#scroll" class="link">Fixed Header: Click Me!</a></div>

<div class="foo">foo: section</div>
<div class="foo">foo: section</div>
<div class="main" id="scroll">
  <div class="main2">main: section</div>
</div>
<!-- <div class="main" id="scroll">main</div> -->
Magnus
  • 6,791
  • 8
  • 53
  • 84
  • the same problem is explained here : https://stackoverflow.com/questions/48731110/strange-behavior-of-background-when-elements-are-overlapping ... and basically the solution is to play with painting order by adding removing properties that will change the order, so yes adding position:relative to some element (without the need of z-index) solve the issue – Temani Afif Sep 10 '18 at 23:59
  • Haha, hi @TemaniAfif . Its been a while! Hope all is well. A bit frustrating to have to go and position every target element, but oh well... This functionality is so standard, it should be built into bootstrap or other libraries by default. – Magnus Sep 11 '18 at 13:58
  • by the way where is your other question dealing with this? am not able to find it (the one that get wrongly close). You delete it? – Temani Afif Sep 11 '18 at 21:37
  • @TemaniAfif Check out my comments on the answer below. Any thoughts? – Magnus Sep 11 '18 at 22:10
  • @TemaniAfif Regarding your other comment above, I have no idea. Way overdue on sleep here, but can check tomorrow. Thanks for all the good discussions btw. – Magnus Sep 11 '18 at 22:11

2 Answers2

2

Is there a better way?

it depends on what you mean by better. In all the cases, the solution shouldn't break any other functionality or the layout then we can consider it as a good solution.

Also, is this the typical way this problem is solved?

The problem as you already noticed, involve painting order so the typical way to solve such issue is to add/change some properties in order to adjust the painting order like we want. You may also notice that not only z-index changes order but other properties like transform, filter, opacity, float, etc.


For this particular case, you don't need to adjust z-index and make all the element positioned. You simply need to increase the z-index of the fixed header and make the scrolling element positioned:

body {
  height: 300vh;
  text-align: center;
  width: 100%;
}

.header {
  height: 40px;
  width: 100%;
  background-color: yellow;
  position: fixed;
  z-index:1;
  top: 0;
}

.foo {
  height: 50px;
  background-color: grey;
}

.foo:nth-child(2) {
  margin-top: 40px;
  background-color: red;
}

.main {
  height: 100px;
  position:relative;
  background: blue;
}

.main div {
  height: 100px;
}

.main::before {
  content: "pseudo-element (when removing its background-color, you see how .main unfortunately paints on top of foo)";
  display: block;
  background: green;
  height: 40px;
  margin-top: -40px;
}

.main2 {
  height: 100px;
  background-color: blue;
}
<div class="header">
  <a href="#scroll" class="link">Fixed Header: Click Me!</a></div>

<div class="foo">foo: section</div>
<div class="foo">foo: section</div>
<div class="main" id="scroll">
  <div class="main2">main: section</div>
</div>
<!-- <div class="main" id="scroll">main</div> -->
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
1

You can add an invisible anchor to be the target of the top link, and then add top padding equal to your header to that target:

body {
  height: 300vh;
  text-align: center;
  width: 100%;
  padding: 0px;
  margin: 0px;
}

.header {
  height: 40px;
  width: 100%;
  background-color: yellow;
  position: fixed;
  top: 0;
}

#anchor {
  padding-top: 40px;
  visibility: hidden;
}

.foo {
  height: 50px;
  background-color: grey;
}

.foo:nth-child(2) {
  margin-top: 40px;
  background-color: red;
}

.main {
  height: 100px;
  background: blue;
}

.main div {
  height: 100px;
}

.main2 {
  height: 100px;
  background-color: blue;
}
<div class="header">
  <a href="#anchor" class="link">Fixed Header: Click Me!</a></div>

<div class="foo">foo: section</div>
<div class="foo">foo2ndchild: section</div>
<div class="main" id="scroll">
  <a id="anchor">not shown</a>
  <div class="main2">scrolling to this section</div>
</div>
Scott Weaver
  • 7,192
  • 2
  • 31
  • 43