web-dev-qa-db-fra.com

Comment utiliser CSS (et JavaScript?) Pour créer un arrière-plan flou "givré"?

J'essaie de créer un look dépoli de style iOS 7 avec HTML5, CSS3 et JavaScript qui peut fonctionner sur les navigateurs webkit.

Techniquement, étant donné le HTML suivant:

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

Je voudrais appliquer quelque chose comme une -webkit-filter: blur(5px) aux 20 premiers pixels horizontalement de #main-view.

Si le CSS a été modifié pour être #partial-overlay { width: 20px; height: 100%; ...}, Alors je devrais appliquer la -webkit-filter: blur(5px) aux 20 premiers pixels verticalement.

La solution évidente consiste à utiliser javascript pour faire un clone du #main-view, Définir overflow: hidden Puis changer la largeur/hauteur selon les besoins mais cela me semble difficile à généraliser à des pages/CSS plus complexes structures.

Existe-t-il un meilleur moyen d'y parvenir avec un minimum de performances et une généralisabilité maximale?

EDIT : Voici un exemple de ce que j'essaie de réaliser: Mockup

50
Aaron Yodaiken

Merci pour l'inspiration ... Ça m'a conduit à ça plugin canvas qui fait l'affaire

Nouveau et amélioré: - exemple de travail webkit et Firefox, maintenant redimensionnable/fluide.

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>

Je l'ai mis dans une fonction de clic, car je pensais que ce serait le cas d'utilisation le plus probable. Cela fonctionnera tout aussi bien sur un document prêt.

Bien que la représentation de la toile ne soit pas parfaite en pixels, je ne pense pas que cela importera vraiment dans la plupart des cas car elle est floue.

Mise à jour: Comme demandé, il est maintenant redimensionnable. J'ai également déplacé la div de couverture dans le JS et ajouté un svg de secours pour Firefox. Le redimensionnement nécessite que le canevas soit redessiné à chaque redimensionnement, je l'ai donc configuré pour masquer le canevas, la superposition, etc. pendant le redimensionnement, puis le remplacer lorsque la redimensionnement s'arrête.

20
apaul

Fondamentalement, vous pouvez avoir un espace réservé de superposition où vous dupliquez le contenu principal et synchronisez le défilement des deux divs, puis appliquez le filtre de flou CSS sur la superposition uniquement.

Un simple javascript fera:

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

Et pour le CSS:

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

J'ai mis en place un exemple de travail pour vous (webkit uniquement):

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

S'amuser! :)

14
Gerson Goulart

Existe-t-il un meilleur moyen d'y parvenir avec un minimum de performances et une généralisation maximale?

La réponse à cette question est non .

La raison en est que pour faire ce que vous voulez, vous aurez besoin d'un accès direct au bitmap utilisé pour la fenêtre du navigateur pour extraire ou manipuler les pixels dans la zone que vous souhaitez brouiller (je souhaite, "aéro" dans un navigateur pourrait sembler joli neat ..) ou un filtre qui fonctionne sur les éléments derrière celui auquel vous l'appliquez (ou peut avoir une région limitante définie).

Comme il n'y a pas de support natif pour le faire (à part l'API de canevas et d'extension, ou une bibliothèque qui génère une image de canevas à partir du HTML -> relativement lent), cela devra être fait avec ruse (images, diviser les divs, etc.) dans les deux cas. .

Si vous avez tout fait dans votre page sur un canevas, vous pourriez faire beaucoup de choses intéressantes, mais vous auriez également besoin d'effectuer vous-même la mise en page, la mise à jour, le filtrage, etc. et vous ne seriez donc pas de retour non optimisé car Javascript est plus lent que natif (sans oublier que ce serait sujet à erreur).

Jusqu'à ce que les navigateurs vous permettent de saisir une section de la fenêtre en tant que toile (jamais? Car cela nécessiterait que tout sur cette page soit de la même origine ou que le contenu avec des en-têtes d'acceptation spéciaux soient définis), il n'y a pas d'autre moyen que de faire des astuces.

Mise à jour

Pour démontrer que vous pouvez le faire en utilisant html2canvas, etc., mais que vous devez utiliser des compromis (-> performances lentes) - la démo est approximative, expérimentale et nécessite des ajustements pour bien fonctionner - mais pour le plaisir de la démo uniquement:
http://jsfiddle.net/AbdiasSoftware/RCaLR/

Résultat:

enter image description here

Fonction généralisée pour saisir une partie du fond:

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

Obtenez une partie de la fenêtre en utilisant 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)
    });
}

Traitez le contenu:

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);
}
11
user1693593

Récemment, un nouveau -webkit-backdrop-filter. Ceci est actuellement pris en charge dans Safari 9.0 et Chrome derrière un indicateur.

Démo:

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

Le support en ce moment n'est que Safari. Chrome et Opera ont ceci sous un drapeau).

Remarque: Aujourd'hui -webkit-backdrop-filter n'a toujours pas beaucoup de support donc pour l'instant si vous voulez obtenir cet effet, le meilleur moyen est d'utiliser feGaussianBlur de SVG

10
Downgoat

J'ai fait quelques trucs css sans backdrop-filter car chrome ne le prend pas en charge par défaut

Mon code d'origine avec 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
MixerOID

http://thiv.net/frost

Échantillon en direct de ce que j'ai fait (et mis à jour pour ressembler à l'image ci-dessus)

Code:

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

Je l'ai fait paraître un peu plus joli qu'il ne devrait l'être, mais ça marche!

3
Connor

Il a toujours un support très limité (uniquement Firefox) mais une façon de l'obtenir pourrait être la suivante:

Démo Firefox uniquement

Le CSS est assez simple:

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

et la clé est d'utiliser comme arrière-plan pour la superposition partielle de l'élément de vue principale.

Cette démo utilise uniquement l'opacité car les filtres ne sont pas disponibles pour Firefox.

La propriété d'élément pour l'arrière-plan a été approuvée par w3c, elle pourrait donc s'afficher dans le futur dans webkit

Dans la démo, la superposition partielle a été déplacée vers la droite pour clarifier ce qui est quoi

2
vals

Malheureusement, il n'y a pas de bonne façon de le faire, comme vous l'avez compris, vous aurez besoin d'une copie de la div principale:

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

La superposition poussera la largeur de l'enveloppe autour tandis que la copie principale sera en arrière-plan avec un flou. Évidemment, il y aura des problèmes de performances si le contenu de la copie principale est complexe.

2
David Nguyen

Solution CSS uniquement: backdrop-filter: blur(4px);

0
Björn Hjorth