2

Why do CSS filters, (ones that seem to have nothing to do with size/positioning) break your ability to give descendant-elements a fixed position?

In addition to answering this question, please suggest a solution that addresses the problem I showcase below.

The CSS and HTML below simply produces a little red box in the center of the viewport:

#box
{
  background: red; color: white;
  display: inline-block;
  border: solid #333333 10px;
  padding: 5px;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Before Filter Corrupts Fixed Positioning</title>
</head>
<body>
<div id ="box">Dead Center<div>
</body>
</html>

Now, let's assume we have a requirement that this whole page must be displayed in grayscale. The only effective change below, is the addition of a CSS grayscale filter. However, upon adding this filter, the box will no longer honor the center page positioning we prescribed:

body { filter: grayscale(100%); }
#box
{
  background: red; color: white;
  display: inline-block;
  border: solid #333333 10px;
  padding: 5px;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Filter Corrupts Fixed Positioning</title>
</head>
<body>
<div id ="box">Dead Center<div>
</body>
</html>

Notice that the box is no longer centered vertically. Is this a bug, or is it just stupid by design?

Update 1:

Temani Afif recommended, in the comments, applying the filter on the html element (instead of the body element). While this does fix the issue in Chrome, it doesn't in Firefox 78:

html { filter: grayscale(100%); }
#box
{
  background: red; color: white;
  display: inline-block;
  border: solid #333333 10px;
  padding: 5px;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Filter Corrupts Fixed Positioning</title>
</head>
<body>
<div id ="box">Dead Center<div>
</body>
</html>

Update 2:

Based on feedback, here I try applying the filter to :root, instead of html. While this does fix the issue in Chrome, it doesn't in Firefox 78:

:root { filter: grayscale(100%); }
#box
{
  background: red; color: white;
  display: inline-block;
  border: solid #333333 10px;
  padding: 5px;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Filter Corrupts Fixed Positioning</title>
</head>
<body>
<div id ="box">Dead Center<div>
</body>
</html>

enter image description here

I submitted this issue to Firefox.

Summary: Even though the spec allows you to apply the filter to the document root, to avoid the encapsulation of fixed/absolute-descendants, I'm of the opinion that the spec could be improved by avoiding this behavior altogether on filters that have nothing to do with modifying size and position. Filters like grayscale should have zero impact on the size or position of descendants and therefore it shouldn't matter where you apply that filter (root or not). On filters like grayscale, there should never be any wrapping of descendants. I am explaining it to the W3C here.

Update 3: @NateG recommended applying the filter to body > *. So far, that seems to work in both Chromium and Firefox! See below:

body > * { filter: grayscale(100%); }
#box
{
  background: red; color: white;
  display: inline-block;
  border: solid #333333 10px;
  padding: 5px;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Filter Corrupts Fixed Positioning</title>
</head>
<body>
<div id ="box">Dead Center<div>
</body>
</html>
halfer
  • 19,824
  • 17
  • 99
  • 186
Lonnie Best
  • 9,936
  • 10
  • 57
  • 97
  • 1
    an easy fix is to make sure your body element is having min-height:100vh OR apply the filter to html instead – Temani Afif Jul 03 '20 at 14:24
  • @TemaniAfif : Unfortunately, applying the filter to the html element (instead of the body element) doesn't fix the problem. See `Update 1` above. – Lonnie Best Jul 03 '20 at 15:35
  • 2
    Does this answer your question? [CSS-Filter on parent breaks child positioning](https://stackoverflow.com/questions/52937708/css-filter-on-parent-breaks-child-positioning) – tris timb Jul 03 '20 at 22:56
  • @tristimb Thanks for that posting that link. Although the link does touch on the problem, my focus here is grayscaling the whole webpage without effecting the position of absolute and fixed descendants. So, far no suggestion I've read (via all links suggested) accomplishes this. – Lonnie Best Jul 04 '20 at 06:03
  • Firefox Bug: [1650522](https://bugzilla.mozilla.org/show_bug.cgi?id=1650522) – Lonnie Best Jul 04 '20 at 06:53
  • @TemaniAfif : Your suggestion of setting `min-height:100vh` works in both Firefox and Chome for the simplified example I've provided and it also works in Chrome for my actual application. However, it doesn't work in Firefox for my actual application. – Lonnie Best Jul 04 '20 at 09:21
  • 1
    I think your 'stacking context'eses get messed up. Checkout [MDN: The stacking context](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context) on criteria WHEN a new stacking context will be created.... Sure you are addressing the right parent in that respect? Hint: includes 'filter'. – Rene van der Lende Jul 07 '20 at 02:23
  • @RenevanderLende Yes, that's what they're telling me over at the [w3c](https://github.com/w3c/fxtf-drafts/issues/402#issuecomment-653907865) too. – Lonnie Best Jul 07 '20 at 20:24

3 Answers3

2

You should not mess with the body/html when applying some styles because you will face a lot of unexpected result due to the nature of those elements.

Related question to see that you will face more issues than expected playing with filter on html: CSS filter:invert not working with background-color

You need to also consider propagation for some properties like overflow and background

Here is one idea to simulate your fixed element using position:sticky and considering an extra wrapper to avoid any kind of issue:

.html {
  min-height: 100vh;
  filter: grayscale(100%);
}

#box {
  background: red;
  color: white;
  border: solid #333333 10px;
  padding: 5px;
  position: sticky;
  top: 50vh;
  left: 50%;
  transform: translate(-50%, -50%);
  /* the combination of float and transparent shape outside will make the element
     * shrink to fit
     * will not affect the other elements
  */
  float: left;
  shape-outside: linear-gradient(transparent, transparent);
}


/* to simulate content */

.content {
  font-size: 40px;
}

body {
  margin: 0;
}
<div class="html">
  <div id="box">Dead Center
  </div>
  <div class="content">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus volutpat eu ante sed lacinia. In sit amet sapien euismod nibh malesuada convallis pulvinar non mauris. Ut id felis posuere, pharetra justo eget, viverra lacus. Vestibulum tellus libero,
    euismod ac tellus vitae, dapibus mollis tellus. Donec ornare consectetur dui. Vestibulum iaculis neque leo, nec bibendum nisl consectetur in. Curabitur at viverra augue, ac semper neque.
  </div>
</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • I would also recommend a wrapper over adding styles directly to the body – tris timb Jul 04 '20 at 15:14
  • In my case, I do not have the liberty of modifying the HTML at all. I'm trying to fix a bug that's in the [Dark Mode](https://addons.mozilla.org/en-US/firefox/addon/dark-mode-webextension/) Firefox browser extension. To see this, you'd have to install the extension in Firefox and then go to its settings: looking at the Class B Themes it offers. Those "invert" styles break the positioning of fixed and absolute descendants. So you see, I'm not trying to apply this to just one page on the internet, I'm trying to apply it to all web pages on the internet: making the whole web "Dark Mode". – Lonnie Best Jul 05 '20 at 08:15
  • @LonnieBest you have to find something else than filter then. You have no luck with filter if you want to consider all the pages. You will for sure break things. – Temani Afif Jul 05 '20 at 08:21
  • Check out Update 3, in my question. I don't yet understand why, but that seems to work so far. – Lonnie Best Jul 05 '20 at 08:37
  • 1
    @LonnieBest it's working because you no more apply filter to any ancestor of the fixed element so you will not modify its containing block. If you wrap the fixed element inside another div, it will fail. As I explained in my other answer, the fix is to avoid applying the filter to any ancestor BUT in your case, you don't know the page and the fixed element may appear anywhere in the page – Temani Afif Jul 05 '20 at 08:39
  • Well said. Now I understand perfectly. I hope the spec writers come read this thread in its entirety. In my opinion, the spec [needs to be changed](https://github.com/w3c/fxtf-drafts/issues/402). It is shameful that filters like grayscale and invert should have any effect on the positioning of descendants. The spec makes a CSS feature, that would otherwise be universal, limited. CSS shouldn't have to change the structure of HTML/DOM to accomplish filters that are dealing exclusively with color (and have nothing to do with size/position). – Lonnie Best Jul 05 '20 at 09:01
1

In the spec it says

A value other than none for the filter property results in the creation of a containing block for absolute and fixed positioned descendants unless the element it applies to is a document root element in the current browsing context. The list of functions are applied in the order provided.

So your fixed element with id box is being fixed to a new containing block and no longer fixed to the viewport. I also found this much more thorough answer by Temani Afif

Lonnie Best
  • 9,936
  • 10
  • 57
  • 97
tris timb
  • 905
  • 1
  • 7
  • 11
  • 1
    You answer explains what's wrong with the spec. Since `grayscale` has nothing to do with positioning, the spec definitely needs to change the granularity at which it approaches filters. For example, those filters that have absolutely nothing to do with size/positioning, should have no effect on the positioning of descendants. However, in addition to answering why, HOW do you apply a CSS filter to the document root if `html` is not the root (as tried in `Update 1` of my question). – Lonnie Best Jul 04 '20 at 01:45
  • In Update 2, of my question, based on the spec you quoted, I attempted to apply the filter to [:root](https://developer.mozilla.org/en-US/docs/Web/CSS/:root), instead of `body` or `html`, but the fixed positioned descendant (id="box") was still mangled. – Lonnie Best Jul 04 '20 at 06:10
  • 1
    What if you apply the filter to `body > *`? Less performant, but may solve this issue. I admit to not fully considering new issues it may raise, but I can't think of a scenario in which it would alter the containing block of second depth elements. – Nate G Jul 04 '20 at 06:36
  • @NateG Absolutely brilliant! Post that as a formal answer and you'll get my check-mark, sir. See Update 3 in my question. You're suggestion is the only thing that works in both Chromium and Firefox. – Lonnie Best Jul 05 '20 at 04:51
  • 1
    @LonnieBest using `body > *` will not fix the issue but will move the problem to another level. If you have a fixed element not a child of the body you will have the same issue. – Temani Afif Jul 05 '20 at 08:15
1

What if you apply the filter to body > *? Less performant, but may solve this issue. I admit to not fully considering new issues it may raise, but I can't think of a scenario in which it would alter the containing block of second depth elements.

Nate G
  • 132
  • 5
  • This circumvents the [Firefox Bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1650522) in many circumstances, but as Temani Afif warns, it isn't a perfect fix, because it moves the problem to another level. – Lonnie Best Jul 07 '20 at 10:59