Animation: Making an image gradiently appear from side to side (in opacity)(so called “soft-wipe” effect)

吃可爱长大的小学妹 提交于 2019-12-10 17:47:43

问题


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 trying to make can easily be done by video editors, but looks to me not so easy with CSS/JS: First of all, I am not talking about sliding in the image, the image is not moving at all. I want it to appear from side to side, but in a gradient transparent manner. Imagine there is a gradient opacity mask on the image, making the opacity of its left end to be 1, and that of its right end to be 0. When this mask moves from left right, it is the animation I wanna achieve.

I can split the image to several pieces, and manipulate the opacity of each one, there has to be a certain amount pieces to make the whole animation smooth and appeal.

The other way I am thinking is to use canvas, where you can manipulate the image by pixel, as this page suggests, I can do

// get the image data object
var image = ctx.getImageData(0, 0, 500, 200);
// get the image data values 
var imageData = image.data,
length = imageData.length;
// set every fourth value to 50
for(var i=3; i < length; i+=4){  
    imageData[i] = 50;
}
// after the manipulation, reset the data
image.data = imageData;
// and put the imagedata back to the canvas
ctx.putImageData(image, 0, 0);

However the page is only taking about a static image but not animation. I am wondering whether it would undermine performance if I use this approach. Also, this approach involve a lot of ugly calculations.

I think what I want to achieve is not very strange, so is there any javascript plugin to achieve this?


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



回答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.




回答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.



来源:https://stackoverflow.com/questions/36559881/animation-making-an-image-gradiently-appear-from-side-to-side-in-opacityso-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!