How to use CSS (and JavaScript?) to create a blurred, "frosted" background?

34,507

Solution 1

Thanks for the inspiration... It led me to this canvas plugin which does the trick

New and Improved: -webkit- and Firefox Working Example, now re-sizable/fluid.

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>

I put it in a click function, because I figured it would be the most likely use case. It will work just as well on document ready.

Although the canvas representation wont be pixel perfect, I don't think it will really matter in most cases because its being blurred.

Update: As requested this is now re-sizable. I also moved the cover div into the JS and added an svg fall back for Firefox. The resizing requires the canvas to be redrawn on each re-size, so I set it up to hide the canvas, overlay, etc while you're resizing and then replace it when the re-size stops.

Solution 2

Basically you could have a overlay placeholder where you duplicate the main content and sync the scrolling of both divs, than apply css blur filter on the overlay only.

A simple javascript will do:

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

And for the CSS:

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

I put together a working example for you (webkit only):

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

Have fun! :)

Solution 3

Recently, a new -webkit-backdrop-filter. This is currently supported in Safari 9.0, and Chrome behind a flag.

Demo:

#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>

Support right now is only Safari. Chrome and Opera have this under a flag.

Note: Today -webkit-backdrop-filter still doesn't have great support so for now if you want to get this effect, the best way is using SVG's feGaussianBlur

Solution 4

CSS only solution: backdrop-filter: blur(4px);

Solution 5

I've made some css-trick without backdrop-filter because chrome doesn't support it by default

My original code with 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>
Share:
34,507
Aaron Yodaiken
Author by

Aaron Yodaiken

John Jay Scholar of Economics at Columbia. Love coding, designing, and learning to make the world a better place. Learn more about what I’m up to at www.aaronyodaiken.com.

Updated on July 18, 2022

Comments

  • Aaron Yodaiken
    Aaron Yodaiken almost 2 years

    I'm trying to create an iOS 7 style frosted look with HTML5, CSS3 and JavaScript which can work on webkit browsers.

    Technically, given the following 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>
    

    I'd like to apply something like a -webkit-filter: blur(5px) to the first 20px horizontally of #main-view.

    If the CSS was modified to be #partial-overlay { width: 20px; height: 100%; ...} then I'd need to apply the -webkit-filter: blur(5px) to the first 20px vertically.

    The obvious solution is to use javascript to make a clone of the #main-view, set overflow: hidden and then change the width/height as appropriate but that seems to me hard to generalize to more complex pages/CSS structures.

    Is there a better way to achieve this with minimal performance hit and maximal generalizability?

    EDIT: Here is an example of what I'm trying to achieve: Mockup

  • Aaron Yodaiken
    Aaron Yodaiken almost 11 years
    Sorry, I am trying to blur the background of the rectangle, not the rectangle itself.
  • Connor
    Connor almost 11 years
    What do you mean? Inside it? or behind it?
  • Antony
    Antony almost 11 years
    Did you see the image in the question? Yours looks nothing like it.
  • Connor
    Connor almost 11 years
    Thanks man, seems like the asker has gone offline... I can host the page till he comes back... Also what you do come back, what is this being used for? It looks pretty cool!
  • Matthew Blancarte
    Matthew Blancarte almost 11 years
    Hmmmm... I don't think this truly accomplishes what OP is asking for. You've just added two divs that have blurs to make it seem like they have the overlay blur.
  • Connor
    Connor almost 11 years
    Well, its the only way this will work... so... I agree with you but, what else can we give him?
  • Aaron Yodaiken
    Aaron Yodaiken almost 11 years
    It's a good effort, but all you're doing is blurring a white div on top of the unblurred #main-view, which gives an appearance of the partially covered content being slightly blurred.
  • Aaron Yodaiken
    Aaron Yodaiken almost 11 years
    If you could somehow make an example where the background image is made by one of those "javascript screenshot" things and then blurred, that might be a really smart answer.
  • Aaron Yodaiken
    Aaron Yodaiken almost 11 years
    This is just about the trick. The only problem is that the blur fades out at the edges of the canvas (not really a problem around the edges, but definitely at the bottom). Perhaps this can be fixed with some wrapper divs and whatnot, but I couldn't seem to get it to work quickly.
  • Aaron Yodaiken
    Aaron Yodaiken almost 11 years
    It looks decent, but it's still not blurring the same as if the canvas was cut off at 20px or whatever which is what I'm ultimately aiming for.
  • Aaron Yodaiken
    Aaron Yodaiken almost 11 years
    There's something funny going on with text in the blur (it's not fully blurring or maybe the canvas is really not transparent or something like that). See jsfiddle.net/TfWs3/33
  • Connor
    Connor almost 11 years
    Sorry, then its not possible
  • apaul
    apaul almost 11 years
    @AaronYodaiken if you need it to be a little more cross browser compatible check this out, you can use svg as a fall back for firefox jsfiddle.net/apaul34208/TfWs3/61
  • Aaron Yodaiken
    Aaron Yodaiken almost 11 years
    If you could make a contained JS function that does everything with fluid width and whatnot (like Ken did) I'll give you the bounty for you hard work over this. Thanks!
  • Keith
    Keith over 10 years
    I've expanded on this to blur the background using an SVG filter: JS Fiddle - FX only but much quicker than using html2canvas.
  • vals
    vals over 10 years
    @Keith Thanks ! It is a real pitty that the background: element feature isn't more widely available, it would allow a lot of posibilities
  • mredig
    mredig over 9 years
    This seems to be by far the simplest and most elegant solution I've come across.
  • Automatico
    Automatico about 9 years
    This approach is pretty awesome, however I can see some uneven frosting on the top-part of the div. Any idea what might cause this?
  • Gerson Goulart
    Gerson Goulart about 9 years
    @Cort3z, this is due to the fading out of the blur effect (which allows you to see the regular text underneath. To better understand, look at the images in the blur example in this post: html5rocks.com/en/tutorials/filters/understanding-css (note the edges of the image in the right fading to white). To account for that and have a sharp edge with blurred content you'd have to make the blurred content a little bigger and then clip it (or make it go off-canvas) for a sharper edge). I hope I made it clearer... This example should have a sharper edge: jsfiddle.net/kmxD3/105
  • Automatico
    Automatico about 9 years
    @GersonGoulart Awesome! Thanks for the explanation :)
  • Admin
    Admin almost 9 years
    There is a similar question, but I don't feel like digging into this to make it work for that question. Could you offer some input there?
  • trusktr
    trusktr over 8 years
    This is the official solution. Can set this as the answer?
  • Zach Saucier
    Zach Saucier over 8 years
    Thanks vals, as always ;)
  • vals
    vals over 8 years
    @ZachSaucier Hi ! Happy to see you again !. As a strange coincidence, I was in Poland in september (a familiar wedding, nothing related with software :-)
  • Luca Steeb
    Luca Steeb over 8 years
    @trusktr No, because it's only available in Safari, which is not really popular compared to Chrome and Firefox.
  • trusktr
    trusktr over 8 years
    @LucaSteeb Chrome supports it now, behind a flag. Hopefully this becomes the answer in the near future, as backdrop-filter will be the actual way to do it. The other answers are just cheap hacks.
  • Luca Steeb
    Luca Steeb about 8 years
    @trusktr Cheap hacks is maybe a bit exaggerated, but generally you're right. The problem is that even when all new browsers support it, what are you doing with users which are using old browsers - it's probably not a small amount..
  • trusktr
    trusktr about 8 years
    @LucaSteeb Indeed, that's a problem, but doesn't change the fact that the other methods are still just hacks. Native has had this ability forever. It's finally time for the Web to start getting real features.