Search code examples
htmlcssbackground-imagepixelate

If it is possible to animate image pixelation without canvas, using only CSS


So you can get pixelation in CSS by doing the following:

  • Set background-image to a very small image (say 50px or 100px).
  • Set image-rendering: pixelated on the element.

That will give you the pixelated look.

Now I would like to animate this, by replacing the "very small image" with a large image after it finishes downloading by the browser:

let img = new Image()
img.src = largeVersion
img.onload = function(){
  // set css background-image to the new image perhaps, not sure...      
}

The problem is two-fold.

  1. I want to have the background-image using background-size: cover so it properly fills the container element. So you can't use background-size in any pixelation animation.
  2. transform: scale(0.1) (to get close to the original pixelation size) doesn't work because it scales the whole element.

I would like to do something like this: animate transform: scale(x) to go from 50px pixelated image to 2000px unpixelated image, over 0.3 or 0.5 seconds. But that doesn't work. I thought maybe using background-size, but that doesn't work either because of the constraint.

Wondering if there is any way to accomplish this.

I have seen this which does pixelation using canvas. Wondering if there is no other solution that works without using JS/canvas.

<style>
  div {
    background-size: cover;
    background-repeat: no-repeat;
    background-position: center center;
  }
</style>
<div style='background-image: url(/100px.jpg)'></div>


Solution

  • You can do pixelation with an svg filter. You can then animate the filter. to use the filter on a div background you just do filter: url(#filterid)

    put together it looks like this:

    #myDiv::before{
    content:"";
    position:absolute;
    top:0;
    left:0;
    width:100%;
    height:100%;
    filter:url(#pixelate);
    background-size:cover;
    background-image:url(https://images.unsplash.com/photo-1475724017904-b712052c192a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2850&q=80)
    
    }
    
    #myDiv {
      position:relative;
      width:350px;
      height:250px;
    }
    
    .inside {
      position: relative;
    }
    <div id="myDiv"> <div class="inside"><h1>Hello</h1></div> </div>
    <svg>
      <filter id="pixelate" x="0" y="0">
        <feFlood x="4" y="4" height="1" width="1" />
        <feComposite id="composite1" width="10" height="10" />
        <feTile result="a" />
        <feComposite in="SourceGraphic" in2="a" operator="in" />
        <feMorphology id="morphology" operator="dilate" radius="5" />
      </filter>
    
      <animate xlink:href="#composite1" id="anim-width" 
        attributeName="width" from="40" to="10" dur=".8s"
        fill="freeze" />  
      <animate xlink:href="#composite1" id="anim-height" 
        attributeName="height" from="40" to="10" dur=".8s"
        fill="freeze" />
      <animate xlink:href="#morphology" id="anim-radius" 
        attributeName="radius" from="20" to="5" dur=".8s"
        fill="freeze"/>
    </svg>

    Notice I had to create a inner div and apply the background on a pseudo-element ::before but "soon" this will become unnecessary, when the support of backdrop-filter improves.

    references:

    Pixelate svg effect: https://codesandbox.io/s/km3opvn6yv

    Animate an svg filter: https://codepen.io/chriscoyier/pen/dPRVqL

    backdrop-filter: https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter