为什么在父级上应用 CSS 过滤器会破坏子级定位?

问题描述 投票:0回答:4

所以我有这个标题屏幕“动画”,其标题位于全屏页面的中心,当您向下滚动时,它会变小并保留在页面顶部。这是一个具有预期行为的工作示例,我从中删除了所有不必要的代码以使其最小化:

$(window).scroll( () => {
    "use strict";
    let windowH = $(window).height();
    let windowS = $(window).scrollTop();
    let header  = $("#header").height(); 
    
    if (windowS < windowH-header) {
        $("#title").css("transform", "scale("+(2-(windowS/($(document).outerHeight()-windowH))*2.7)+")");
        $("#header").css("transform", "translateY(0)");
        $("#inside, #content").css({
            "position": "static",
            "margin-top": 0
        });
    } else {
        $("#inside").css({
            "position": "fixed",
            "margin-top": -windowH+header
        });
        $("#content").css("margin-top", windowH);
    }
  
    $("#header").css("position", windowS > (windowH-header)/2 ? "fixed" :"static");
});
.fixed {
    position: fixed!important;
}
.wrapper {
    width: 100%;
    text-align: center;
}
.wrapper:before {
    display: table;
    content: " ";
}
.wrapper:after {
    clear: both;
}
#inside {
    width: 100%;
    height: 100vh;
    background-color: lightcoral;
    display: flex;
    align-items: center;
    justify-content: center;
}
#header {
    height: 90px;
    top: 0;
    position: sticky;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.5s;
}
#title {
    width: 100%;
    color: #fff;
    transform: scale(2);
}
#content {
    height: 1000px;
    background-color: lightblue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>  
    <div class="wrapper">
        <div id="inside">
            <div id="header">
                <h1 id="title">Title</h1>
            </div>
        </div>
    <div id="content"></div>
</body>

接下来是完全相同的片段,但添加了一个内容:我应用了一个过滤器,就我而言,这纯粹是装饰性的:

filter: brightness(1.3);

如下所示,当您滚动“动画”一半时,标题就会消失。当您检查该元素时,它仍然具有其所有属性,但不知何故它已经消失了。这在 Firefox 和 Chrome 中是一样的,我不知道为什么。如果有人可以发布应用了过滤器的工作片段并解释为什么它之前不起作用,我将不胜感激。

$(window).scroll( () => {
    "use strict";
    let windowH = $(window).height();
    let windowS = $(window).scrollTop();
    let header  = $("#header").height(); 
    
    if (windowS < windowH-header) {
        $("#title").css("transform", "scale("+(2-(windowS/($(document).outerHeight()-windowH))*2.7)+")");
        $("#header").css("transform", "translateY(0)");
        $("#inside, #content").css({
            "position": "static",
            "margin-top": 0
        });
    } else {
        $("#inside").css({
            "position": "fixed",
            "margin-top": -windowH+header
        });
        $("#content").css("margin-top", windowH);
    }
  
    $("#header").css("position", windowS > (windowH-header)/2 ? "fixed" :"static");
});
.fixed {
    position: fixed!important;
}
.wrapper {
    width: 100%;
    text-align: center;
}
.wrapper:before {
    display: table;
    content: " ";
}
.wrapper:after {
    clear: both;
}
#inside {
    width: 100%;
    height: 100vh;
    background-color: lightcoral;
    filter: brightness(1.3);        /*<<<<<<<<<<<<<<<<*/
    display: flex;
    align-items: center;
    justify-content: center;
}
#header {
    height: 90px;
    top: 0;
    position: sticky;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.5s;
}
#title {
    width: 100%;
    color: #fff;
    transform: scale(2);
}
#content {
    height: 1000px;
    background-color: lightblue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>  
    <div class="wrapper">
        <div id="inside">
            <div id="header">
                <h1 id="title">Title</h1>
            </div>
        </div>
    <div id="content"></div>
</body>

css css-position css-transforms css-filters
4个回答
170
投票

如果我们参考规范我们可以读到:

过滤器属性的值不是 none 会导致 为绝对和固定定位创建包含块 后代,除非它适用的元素是文档根 当前浏览上下文中的元素。函数列表是 按照提供的顺序申请。

这意味着您的

position:fixed
元素将相对于过滤后的容器定位,而不再是视口。换句话说,它仍然是固定的,但位于其new包含块(过滤容器)

这是一个简化版本来说明问题:

.container {
  display: inline-block;
  width: 200px;
  height: 200vh;
  border: 1px solid;
}

.container>div {
  position: fixed;
  width: 100px;
  height: 100px;
  background: red;
  color: #fff;
}
<div class="container">
  <div>I am fixed on scroll</div>
</div>

<div class="container" style="filter:grayscale(1);">
  <div>I move with the scroll</div>
</div>

要解决此问题,请尝试将过滤器移动到固定元素而不是其容器:

.container {
  display: inline-block;
  width: 200px;
  height: 200vh;
  border: 1px solid;
}

.container>div {
  position: fixed;
  width: 100px;
  height: 100px;
  background: red;
  color: #fff;
  filter: grayscale(1);
}
<div class="container">
  <div>I am fixed on scroll</div>
</div>


这里是一个非详尽的1属性列表,导致为绝对和固定定位的后代创建包含块

如果属性的任何非初始值都会导致元素为绝对定位元素生成包含块,则在 will-change 中指定该属性必须导致元素为绝对定位元素生成包含块。 参考


1:我会尽力使此列表保持最新。


4
投票

您可以使用

before
来实现它。而不是直接将
filter: brightness(1.3);
分配给子元素。为该子元素的
before
指定颜色和滤镜。所以你的代码应该是这样的:

  #inside:before{
    filter: brightness(1.3);
    content: "";
    background-color: lightcoral;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    position: absolute;
}

这将解决问题。

您还应该编辑一些代码以使其完全正常工作:

#inside{
    position: relative;
}

使父级位置相对,以确保

before
包裹在其父级内。

此解决方案还适用于其他过滤器,例如

backdrop-filter


2
投票

为了避免维护困难和每个浏览器的不同实现,您可以使用递归树遍历功能来标记位置“固定”节点,然后在不破坏位置的情况下正确应用所需的过滤器。

我使用 this 代码是为了全局应用“过滤器反转”属性,而不将其硬编码到特定的父元素。


1
投票

如果您想对除一个元素之外的整个页面进行模糊或灰度化,只需使用

backdrop-filter
而不是
filter
。 在撰写本文时,Firefox 只需要默认 ship 即可。

不工作:

<!DOCTYPE html><html><head>
    <style>
    .overlay {
        position:fixed;
        right:10%;
        top:10%;
        bottom:10%;
        left:10%;
        background-color:red;
        z-index:2;
    }
    .overlay ~ * {
        filter: grayscale(50%) blur(5px);
    }
    </style>
</head>
<body>
    <div class="overlay">I am not blurred</div>

    <!-- position test -->
    <div style="margin-top:100px;float:right;">
        <div style="background-color:red;position:fixed;top:0;right:0;width:100px;height:100px;"></div>
    </div>
</body>
</html>

工作:

<!DOCTYPE html><html><head>
    <style>
    .overlay {
        position:fixed;
        right:10%;
        top:10%;
        bottom:10%;
        left:10%;
        background-color:red;
        z-index:2;
    }
    body:after {
        content:"";
        position:fixed;
        z-index: 1;
        top:0;
        left:0;
        width:100%;
        height:100%;
        backdrop-filter: grayscale(50%) blur(5px);
    }
    </style>
</head>
<body>
    <div class="overlay">I am not blurred</div>

    <!-- position test -->
    <div style="margin-top:100px;float:right;">
        <div style="background-color:red;position:fixed;top:0;right:0;width:100px;height:100px;"></div>
    </div>
</body>
</html>

© www.soinside.com 2019 - 2024. All rights reserved.