如何使用CSS(和JavaScript?)创建模糊的“霜化”背景?

53

我正在尝试使用HTML5、CSS3和JavaScript创建一个类似于iOS 7风格的毛玻璃效果,可以在Webkit浏览器上运行。

从技术上讲,给定以下HTML:

<style>
  #partial-overlay {
    width: 100%;
    height: 20px;
    background: rgba(255, 255, 255, .2); /* TODO frost */
    position: fixed;
    top: 0;
    left: 0;
  }
</style>
<div id="main-view">
  <div style="width: 50px; height: 50px; background: #f00"></div>
  To my left is a red box<br>
  Now there is just text<br>
  Text that goes on for a few pixels <br>
  or even more
</div>
<div id="partial-overlay">
  Here is some content
</div>

我希望对#main-view的前20像素水平应用类似于-webkit-filter: blur(5px)的效果。

如果CSS被修改为#partial-overlay { width: 20px; height: 100%; ...},那么我需要在前20个垂直像素上应用-webkit-filter: blur(5px)

明显的解决方案是使用JavaScript克隆#main-view,设置overflow:hidden,然后根据需要更改宽度/高度,但我认为这很难普遍适用于更复杂的页面/CSS结构。

有没有更好的方法来实现这一点,以最小化性能损失和最大化通用性?

编辑:这是我想要实现的例子:Mockup


CSS是一种全局性的东西。你不能仅将属性应用于任意部分。最接近“仅前20像素”的方法是使用:first-line伪元素。 - cimmanon
@cimmanon 请查看已编辑的“显而易见的解决方案”。 - Aaron Yodaiken
你能展示一张图片来说明你想要做什么吗?如果我正确理解了问题,你可以尝试使用CSS的多重背景能力来实现这个目标。 编辑:此外,这是他的示例的JSFIDDLE链接,供回答者测试:http://jsfiddle.net/c6Lwf/ - aaron-bond
@cimmanon,我刚试了一下, webkit-filter:blur 似乎不能与 first-line 一起使用,看起来这个伪类只允许使用一些样式 - Subhas
亚伦,我改了我的代码,现在它看起来更像你展示的那张图片了。 - Connor
显示剩余3条评论
10个回答

21

感谢您的启发...它引导我找到了这个画布插件,可以解决问题。

新功能改进:-webkit- 和 Firefox 工作示例,现在可调整大小和适应性布局。

JS

$(document).ready(function () {
    frost = function () {
        var w = $('#main-view').width();
        html2canvas(document.body, {
            onrendered: function (canvas) {
                document.body.appendChild(canvas);
                $('canvas').wrap('<div id="contain" />');
            },
            width: w,
            height: 30
        });
        $('canvas, #partial-overlay, #cover').hide();
        $('#cover').fadeIn('slow', function () {
            $('#partial-overlay').fadeIn('slow');
        });
    };

    $('body').append('<div id="cover"></div><svg id="svg-image-blur"><filter id="blur-effect-1"><feGaussianBlur stdDeviation="2"/></filter></svg>');

    $('#main-view').click(function () {
        frost();
        $('#partial-overlay').addClass('vis');
        $(window).resize(function () {
            $('canvas, #partial-overlay, #cover').hide();
        });

        function onResize() {
            if ($('#partial-overlay').hasClass('vis')) {
                frost();
            }
        }
        var timer;
        $(window).bind('resize', function () {
            timer && clearTimeout(timer);
            timer = setTimeout(onResize, 50);
        });

    });

    $('#partial-overlay').click(function () {
        $('#partial-overlay').removeClass('vis');
        $('canvas, #partial-overlay, #cover').hide();
    });
});

CSS

#main-view {
    width:75%;
    height:50%;
    box-sizing: border-box;
    margin:8px;
}
#partial-overlay {
    display:none;
    width: 100%;
    height: 20px;
    position: absolute;
    top: 0;
    left: 0;
    z-index:99;
    background: rgba(255, 255, 255, 0.2);
    cursor:pointer;
}
canvas {
    position: absolute;
    top: 0;
    left: 0;
    -webkit-filter:blur(5px);
    filter: url(#blur-effect-1);
}
#cover {
    display:none;
    height:19px;
    width:100%;
    background:#fff;
    top:0;
    left:0;
    position:absolute;
}
#contain {
    height:20px;
    width:100%;
    overflow:hidden;
    position:absolute;
    top:0;
    left:0;
}
svg {
    height:0;
    width:0;
}

HTML

<div id="main-view">
    <div style="width: 10%; height: 20%; background: #f00; float: left"></div>To my left is a red box
    <br>Now there is just text
    <br>Text that goes on for a few pixels
    <br>or even more</div>
<div id="partial-overlay">Here is some content</div>

我将其放在点击函数中,因为我认为这是最有可能的用例。它在文档准备好时同样有效。

虽然画布表示不会完美呈现像素,但我认为在大多数情况下这并不重要,因为它被模糊了。

更新:按要求,现在可以重新调整大小。我还将覆盖div移动到了JS中,并为Firefox添加了SVG回退。重新调整大小需要在每次重新调整大小时重新绘制画布,所以我设置了隐藏画布,覆盖等,当你调整大小时,然后在调整大小停止时替换它。


如果你能够创建一个示例,其中背景图像是由其中一种“JavaScript截图”生成的,然后进行模糊处理,那可能是一个非常聪明的答案。 - Aaron Yodaiken
看起来还不错,但是它仍然没有像画布被截断在20像素或其他值时那样模糊,这也是我最终的目标。 - Aaron Yodaiken
文本模糊效果存在问题(可能没有完全模糊或画布不够透明等),请参见http://jsfiddle.net/TfWs3/33/。 - Aaron Yodaiken
如果您需要更好地兼容各种浏览器,请查看此链接,您可以使用SVG作为Firefox的回退选项。 - apaul
如果您能够创建一个包含JS函数,可以使用流体宽度等功能完成所有操作(就像Ken所做的那样),我会为您在此工作上的辛勤付出提供赏金。谢谢! - Aaron Yodaiken
显示剩余2条评论

13

基本上,您可以使用遮罩占位符,在其中复制主要内容并同步滚动两个div,然后仅在遮罩层上应用CSS模糊滤镜。

一个简单的JavaScript就可以实现:

$(document).ready(function(){
  $('.overlay').append( $('.content').html() );
  $('.content').on('scroll', function(){
    $('.overlay').scrollTop($(this).scrollTop());
  });
});

并且对于CSS:

.overlay {
    overflow:hidden;
    -webkit-filter: blur(.25em);
    background:#fff;
}

我为您制作了一个可用的示例(仅适用于webkit):

http://jsfiddle.net/kmxD3/1/

玩得开心!:)


3
我遇到的这个方案似乎是迄今为止最简单和最优雅的解决方案。 - mredig
这种方法非常棒,但我发现div的顶部有一些不均匀的霜。你有什么想法是什么原因造成的吗? - Automatico
2
@Cort3z,这是由于模糊效果的淡出(它允许您看到正常文本下面的内容)。为了更好地理解,请查看此帖子中模糊示例中的图像:http://www.html5rocks.com/en/tutorials/filters/understanding-css/(请注意右侧图像的边缘向白色淡化)。为了考虑到这一点并具有带有模糊内容的锐利边缘,您需要使模糊内容稍微变大,然后剪辑它(或使其超出画布)以获得更锐利的边缘。我希望我已经让它更清晰了...此示例应该具有更锐利的边缘:http://jsfiddle.net/kmxD3/105/ - Gerson Goulart
@GersonGoulart 太棒了!感谢您的解释 :) - Automatico

11
有没有更好的方法来实现这个目标,既能最小化性能损失,又能最大程度地通用化?
答案是“没有”。
原因是,为了做到你想要的,你需要直接访问用于浏览器窗口的位图,以提取或操作你想要模糊的区域中的像素(我希望,在浏览器中使用“aero”看起来非常漂亮...),或者使用在应用它的元素后面工作的过滤器(或可以将限制区域设置为它)。
由于没有本地支持来做到这一点(除了画布和扩展API,或从HTML生成画布图像的库 - 相对较慢),因此这将需要使用诡计(图像、分割div等)来完成。
如果您在页面上的所有内容都在画布上完成,您可以做很多有趣的事情,但您也需要自己执行布局、更新、过滤等操作,因此您会回到非优化状态,因为JavaScript比本地代码慢(更不用说它容易出错了)。

直到浏览器允许您抓取窗口部分作为画布(永远不可能?因为这将要求页面上的所有内容具有相同的来源或具有设置了特殊接受标头的内容),否则就没有办法绕过它,只能使用一些技巧。

更新

作为一个演示,您可以使用html2canvas等工具做到这一点,但必须使用妥协(->性能慢)-演示程序粗糙、实验性,并需要进行调整才能正常工作-但仅供演示使用:
http://jsfiddle.net/AbdiasSoftware/RCaLR/

结果:

enter image description here

抓取背景的通用函数:

getBlurredBG(x, y, width, height, id);

使用html2canvas获取窗口的一部分:
function getBlurredBG(x, y, w, h, id) {

    html2canvas(document.body, {
        onrendered: function (canvas) {
            process(canvas, x, y, w, h, id);
        },
        width: (x + w),
        height: (y + h)
    });
}

处理内容:

function process(canvas, x, y, w, h, id) {

    //create new canvas to enable clipping
    //As html2canvas returns a canvas from 0,0 to width and height
    //we need to clip it.
    var ocanvas = document.createElement('canvas');
    ocanvas.width = w;
    ocanvas.height = h;
    ocanvas.style.left = x + 'px';
    ocanvas.style.top = y + 'px';
    ocanvas.style.position = 'absolute';
    ocanvas.id = id;

    var ctx = ocanvas.getContext('2d');
    ctx.drawImage(canvas, x, y, w, h,
                          0, 0, w, h);

    stackBlurCanvasRGB(ocanvas, x, y, w, h, 28)
    lighten(ocanvas);

    ctx.fillStyle = 'rgba(255,255,255,0.5)';
    ctx.fillRect(x, y, w, h);

    ctx.fillStyle = '#999';
    ctx.font = '32px arial';
    ctx.fillText("Partial overlay content", 10, 60);

    document.body.appendChild(ocanvas);
}

1
我知道有一些东西可以截取整个页面的屏幕截图,然后你可以发送为PNG或其他格式(就像Google+以前有的那样)。这不是window -> canvas吗? - Aaron Yodaiken
@AaronYodaiken 这是画布。像 html2canvas 这样的库可以做到这一点。但它并不是一个实际的快照,而是基于元素几何形状(元素->边界->类型->渲染近似)的近似表示。对于非原始图像,它需要使用代理。但对于此目的而言是有效的。 - user1693593
@AaronYodaiken 我在我的回答中提到了这一点,但对于“实时”使用来说可能会很慢(也许优化的部分“快照”就足够好了)。可以始终缓存快照,但用户需要等待。由于没有本地支持,因此总会需要妥协。 - user1693593
非常感谢您的想法和解决方案。如果我能够分配赏金,我一定会这样做。 - Aaron Yodaiken

10

最近出现了一个新的-webkit-backdrop-filter。目前在Safari 9.0及其之后的版本中支持,在Chrome中需要使用一个标志才能开启。

演示:

#blurred {
  -webkit-backdrop-filter: blur(10px);
  width: 200px;
  height: 100px;
  position: fixed;
  top: 50px;
  left: 50px;
  border: 3px solid white;
}
<img src="https://upload.wikimedia.org/wikipedia/commons/e/eb/Ash_Tree_-_geograph.org.uk_-_590710.jpg">

<div id="blurred"> Blurred </div>

目前只有 Safari 支持此功能。Chrome 和 Opera 则需要在标志下启用。

注意:今天 -webkit-backdrop-filter 仍然没有很好的支持,所以现在如果您想获得这种效果,最好的方法是使用SVG的feGaussianBlur


这是官方解决方案。可以将其设置为答案吗? - trusktr
@trusktr 不行,因为它只在Safari中可用,而相比Chrome和Firefox,Safari并不是很流行。 - Luca Steeb
@LucaSteeb Chrome现在支持它,但需要在标志后面启用。希望在不久的将来,这将成为答案,因为backdrop-filter将是实际的解决方法。其他答案只是廉价的hack。 - trusktr
@trusktr 便宜的技巧可能有点夸张,但总的来说你是对的。问题在于即使所有新的浏览器都支持它,你如何处理那些使用旧浏览器的用户——这可能不是一个小数目。 - Luca Steeb
@LucaSteeb 的确,这是一个问题,但这并不改变其他方法仍然只是hack的事实。原生应用程序一直拥有这种能力。现在终于到了让Web开始获得真正功能的时候了。 - trusktr

6
CSS唯一的解决方案: backdrop-filter: blur(4px);

4

由于Chrome默认不支持backdrop-filter,我进行了一些没有使用它的css技巧。

以下是我的原始代码(使用sass): https://codepen.io/mixal_bl4/pen/EwPMWo

$(()=>{
  let sdm = $('.some-draggable-modal');
  sdm.center().draggable();
});

jQuery.fn.center = function () {
    this.css("position","absolute");
    this.css("top", Math.max(0, (($(window).height() - $(this).outerHeight()) / 2) + 
                                                $(window).scrollTop()) + "px");
    this.css("left", Math.max(0, (($(window).width() - $(this).outerWidth()) / 2) + 
                                                $(window).scrollLeft()) + "px");
    return this;
};
body {
  background: -webkit-repeating-linear-gradient(135deg, #fff, #fff 25px, #e2edc1 25px, #e2edc1 50px) fixed;
  background: repeating-linear-gradient(-45deg, #fff, #fff 25px, #e2edc1 25px, #e2edc1 50px) fixed;
  background-attachment: fixed;
  background-size: cover;
}
html:before,
body:before {
  content: "";
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.3);
}
html .some-draggable-modal,
body .some-draggable-modal {
  width: 150px;
  height: 150px;
  border: 1px solid lime;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  place-content: center;
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
      -ms-flex-direction: column;
          flex-direction: column;
  text-align: center;
  border-radius: 6px;
  cursor: move;
  position: relative;
  overflow: hidden;
}
html .some-draggable-modal .title,
body .some-draggable-modal .title {
  position: relative;
  z-index: 1;
  color: black;
}
html .some-draggable-modal:before,
body .some-draggable-modal:before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: -webkit-repeating-linear-gradient(135deg, #fff, #fff 25px, #e2edc1 25px, #e2edc1 50px) fixed;
  background: repeating-linear-gradient(-45deg, #fff, #fff 25px, #e2edc1 25px, #e2edc1 50px) fixed;
  background-attachment: fixed;
  background-size: cover;
}
html .some-draggable-modal:hover:before,
body .some-draggable-modal:hover:before {
  -webkit-filter: blur(2px);
          filter: blur(2px);
}
html .some-draggable-modal:hover:after,
body .some-draggable-modal:hover:after {
  content: "filter: blur(2px)";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 10px;
  color: green;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>

<div class="some-draggable-modal">
  <span class="title">You can drag me :)</span>
</div>


3

http://thiv.net/frost

这是我做的实时示例(并更新,以使其看起来像上面的图像)。

代码:

<style>
  #partial-overlay {
    width: 100%;
    height: 45px;
    background: #ffffff; /* TODO frost */
    -webkit-opacity:0.70;
    -webkit-filter: blur(5px);
    position: absolute;
    top: 20px;
    left: 0px;
    z-index:5;
  }
  #main-view
  {
   position: fixed;
   top: 20px;
   left: 80px;
   z-index:-1;
  }
</style>
<div id="main-view">
  <div style="width: 50px; height: 50px; background: #f00; position:fixed; left:10px; top: 40px; -webkit-filter: blur(2px); "></div>
    <div style="width: 80px; height: 60px; background: #fff; position:fixed; left:0px; top: 66px; -webkit-filter: blur(5px);"></div>
    <div style="width: 50px; height: 30px; background: #f00; position:fixed; left:10px; top: 60px;"></div>
  <p style="font-family:Sans, Arial, Verdana;">
  To my left is a red box<br>
  Now there is just text<br>
  Text that goes on for a few pixels <br>
  or even more
  </p>
</div>
<div id="partial-overlay">

</div>

我让它看起来比必要的更漂亮,但它有效!

3
抱歉,我想模糊矩形的背景,而不是矩形本身。 - Aaron Yodaiken
你是指里面还是后面? - Connor
2
你看到问题中的图片了吗?你的看起来一点也不像。 - Antony
谢谢,伙计。看起来提问者已经下线了... 我可以在他回来之前托管这个页面... 顺便问一句,你回来后,这是用来干嘛的?看起来相当不错! - Connor
1
这是一个不错的尝试,但你所做的只是在未模糊的#main-view上方模糊了一个白色div,这使得部分被覆盖的内容看起来略微模糊。 - Aaron Yodaiken
显示剩余3条评论

2

很遗憾,没有什么好的方法来完成这个任务。正如你所发现的那样,你需要一个主要div的副本:

<div class="wrapper">
   <div class="overlay"></div>
   <div id="main-copy"></div>
</div>

覆盖层将会推动包装器的宽度,而主要内容则会以模糊的方式呈现在背景中。如果主要内容过于复杂,显然会存在性能问题。


2

它的支持仍然非常有限(仅限Firefox),但获得它的一种方法可能是这样的:

仅适用于Firefox的演示

CSS相当简单:

#partial-overlay {
    width:400px; 
    height:100px; 
    background: -moz-element(#main-view);
    top: 0px;
    left: 200px;
    position: absolute;
    opacity: 0.3;
}

关键是使用主视图元素的部分覆盖背景。

此演示仅使用不透明度,因为 Firefox 不支持滤镜。

该背景的元素属性已经获得 W3C 批准,因此未来可能在 Webkit 中显示。

在演示中,部分覆盖已向右移动,以更清晰地显示区别。


我通过使用SVG滤镜将其扩展以模糊背景:JS Fiddle - 只适用于FX,但比使用html2canvas快得多。 - Keith
@Keith 谢谢!很遗憾,背景元素功能并不是很普遍,它可以提供很多可能性。 - vals
谢谢vals,一如既往的好;) - Zach Saucier
@ZachSaucier 你好!很高兴再次见到你!作为一个奇妙的巧合,我在九月份去了波兰(参加了一场熟悉的婚礼,与软件无关 :-))。 - vals

2
现在你可以使用CSS属性 backdrop-filter
最新版的Chrome、Opera和Edge默认支持它,而Safari和Legacy Edge则需要使用-webkit-前缀来支持,Firefox则需要将layout.css.backdrop-filter.enabled首选项设置为true才能支持无前缀版本。
因此,你的CSS代码应该如下所示:
#partial-overlay {
    width: 100%;
    height: 20px;
    background: rgba(255, 255, 255, .2); /* TODO frost */
    position: fixed;
    top: 0;
    left: 0;
    
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接