Skip to content Skip to sidebar Skip to footer

Animation: Making An Image Gradiently Appear From Side To Side (in Opacity)(so Called "soft-wipe" Effect)

Sorry if the title is confusing. I've tried my best to compose it, if you are going to understand what I am asking, feel free to suggest better title in comment. The animation I am

Solution 1:

The animation I am trying to make can easily be done by video editors, [...] the image is not moving at all. I want it to appear from side to side

In the video industry we call this a soft-wipe (AKA soft edged wipe) and it's not too complicated to make.

All you need is an alpha mask that you can make using a linear gradient. Then use translate properties with the context combined with xor composite mode to animate it.

What xor mode does is to invert the alpha channel based on the alpha channel drawn to it. The advantage of this is that the canvas element get transparent as well, so any background can show through. You can keep the default comp. mode as well which will make the background black instead.

The gradient is made like this (the color values does not matter with xor mode, just the alpha channel values):

var g = ctx.createLinearGradient(0, 0, ctx.canvas.width, 0);
g.addColorStop(0, "rgba(0,0,0,0)");
g.addColorStop(1, "rgba(0,0,0,1)");
ctx.fillStyle = g;

(see this answer for how to avoid "bright line" artifacts by creating a smoothed gradient).

Now create a function that draws a complete frame based on position t which is a normalized value combined with canvas width - have in mind we need the double width to work with: gradient + room for the gradient to exit -

function render(t) {
  var w = t * ctx.canvas.width;                                // width based on t
  ctx.drawImage(img, 0, 0);                                    // render bg. image
  ctx.translate(-ctx.canvas.width + w, 0);                     // translate on x-axis
  ctx.fillRect(0, 0, ctx.canvas.width * 2, ctx.canvas.height); // render gradient mask
}

Call this in an animation loop until t=2, but optionally set globalCompositeOperation to xor and we're good to go. The animation loop itself will reset transformation for us:

Demo

var ctx = c.getContext("2d"),
    img = new Image,
    t = 0, step = 0.02

// alpha mask
var g = ctx.createLinearGradient(0, 0, ctx.canvas.width, 0);
g.addColorStop(0, "rgba(0,0,0,0)");
g.addColorStop(1, "rgba(0,0,0,1)");
ctx.fillStyle = g;
ctx.globalCompositeOperation = "xor";

// load bg image
img.onload = animate;
img.src = "http://i.imgur.com/d0tZU7n.png";

function animate() {
  ctx.setTransform(1,0,0,1,0,0);                  // reset any transformations
  ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
  render(t);
  t += step;
  if (t <= 2) requestAnimationFrame(animate);     // 2 since we need double width
  else {t=0; setTimeout(animate, 2000)};          // just to repeat anim. for demo
}

function render(t) {
  var w = t * ctx.canvas.width;                   // width based on t
  ctx.drawImage(img, 0, 0);
  ctx.translate(-ctx.canvas.width + w, 0);         // translate on x-axis
  ctx.fillRect(0, 0, ctx.canvas.width*2, ctx.canvas.height);
}
body {background:url(http://i.imgur.com/OT99vSA.jpg) repeat}
<canvas width=658 height=325 id=c></canvas>

Solution 2:

Use the context composite property to do the masking via a gradient. Create an offscreen canvas the same size as the image or the size of the display canvas whichever is smallest.

For every frame create the gradient with the appropriate colour stops (CSS color format rgba(red, green, blue, alpha)) to set the alpha values.

Clear the off screen canvas

ctxOffScreen.clearRect( 0, 0, ctxOffScreen.canvas.width, ctxOffScreen.canvas.height);

Then set the composite value for the of screen canvas to

ctxOffScreen.globalCompositeOperation = "source-over";

Render the image onto it

ctxOffScreen.drawImage(image, 0, 0, ctxOffScreen.canvas.width, ctxOffScreen.canvas.height);

Then set the comp to

ctxOffScreen.globalCompositeOperation = "destination-in";

This will match the pixels already drawn with the same alpha value that is in what every you draw next (the gradient mask)

Then set the fill style to the gradient you created and draw a rectangle over the top

ctxOffScreen.fillStyle = gradient;
ctxOffScreen.fillRect( 0, 0, ctxOffScreen.canvas.width, ctxOffScreen.canvas.height);

Then just render the offscreen canvas to the onscreen canvas

ctx.drawImage(ctxOffScreen.canvas, 0, 0);

If you use

ctxOffScreen.globalCompositeOperation = "destination-out";

instead of "destination-in" you will invert the mask you created with the gradient.


Solution 3:

As an alternative to canvas, you can use svg. CSS gradients are not animateable, however other properties of svg are, so you can find some creative ways to animate gradients after all.

#Mask rect {
  x: 400px;
  transition: 1s;
}

svg:hover #Mask rect {
  x: -400px; 
}

svg {
  border: 2px solid black;
  background-color: #ee3377;
}
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400">
  <defs>
    <linearGradient id="Gradient">
      <stop offset="0" stop-color="white" stop-opacity="0" />
      <stop offset=".5" stop-color="white" stop-opacity="1" />
    </linearGradient>
    <mask id="Mask">
      <rect width="800" height="400" fill="url(#Gradient)"  />
    </mask>
  </defs>
  <image xlink:href="http://i.imgur.com/g3D5jNz.jpg" width="400" height="400" mask="url(#Mask)"></image>
</svg>

You could probably animate the offset property directly, I haven't tested yet.


Post a Comment for "Animation: Making An Image Gradiently Appear From Side To Side (in Opacity)(so Called "soft-wipe" Effect)"