【问题标题】:Animation: Making an image gradiently appear from side to side (in opacity)(so called "soft-wipe" effect)动画:使图像从一侧渐变到另一侧(不透明)(所谓的“软擦除”效果)
【发布时间】:2016-08-02 06:36:41
【问题描述】:

对不起,如果标题令人困惑。我已尽力撰写,如果您想理解我的要求,请随时在评论中建议更好的标题。

我正在尝试制作的动画可以很容易地由视频编辑器完成,但在我看来使用 CSS/JS 并不那么容易:首先,我不是在谈论图像中的滑动,图像不会移动全部。我希望它从一边到另一边出现,但以渐变透明的方式出现。想象一下图像上有一个渐变的不透明度蒙版,使其左端的不透明度为1,右端的不透明度为0。当这个蒙版从左向右移动时,就是我想要实现的动画。

我可以将图像分割成几块,并控制每块的不透明度,必须有一定数量的块才能使整个动画流畅和吸引人。

我想的另一种方法是使用画布,您可以在其中按像素操作图像,正如this page 建议的那样,我可以做到

// 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);

但是,该页面仅采用静态图像而不是动画。我想知道如果我使用这种方法是否会破坏性能。此外,这种方法涉及大量难看的计算。

我想我要实现的东西不是很奇怪,那么有没有什么javascript插件可以实现呢?

【问题讨论】:

  • 是否必须用透明而不是纯色来遮盖图像?图片下面会是什么?
  • 画布在另一个图像之上,所以我想是的,这是必不可少的

标签: javascript animation canvas opacity opacitymask


【解决方案1】:

我正在尝试制作的动画可以很容易地由视频编辑器完成, [...] 图像根本没有移动。我希望它左右出现

在视频行业,我们称之为软擦拭(AKA 软边擦拭),制作起来并不复杂。

您只需要一个可以使用线性渐变制作的 Alpha 蒙版。然后使用带有上下文的 translate 属性并结合 xor 复合模式对其进行动画处理。

xor 模式的作用是根据绘制到它的 Alpha 通道反转 Alpha 通道。这样做的好处是画布元素也变得透明,因此任何背景都可以显示出来。您可以保留默认补偿。模式也会使背景变黑。

渐变是这样制作的(颜色值与 xor 模式无关,只是 alpha 通道值):

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;

(请参阅 this answer,了解如何通过创建平滑渐变来避免“亮线”伪影)。

现在创建一个函数,它根据位置t 绘制一个完整的帧,这是一个结合画布宽度的归一化值 - 请记住,我们需要使用双倍宽度:渐变 + 渐变退出的空间 -

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
}

在动画循环中调用它直到t=2,但可以选择将globalCompositeOperation 设置为xor,我们就可以开始了。动画循环本身将为我们重置转换:

演示

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}
&lt;canvas width=658 height=325 id=c&gt;&lt;/canvas&gt;

【讨论】:

  • 我没有机会消化你的答案,但从 sn-p 我可以看出这就是我想要的。谢谢。不过,接下来几天我可能会有一些后续问题。
  • @shenkwen 没问题。值得一提的是,为了获得更平滑的渐变过渡,您可以像以下答案所示对其进行平滑处理:stackoverflow.com/a/30205767/1693593
  • 在我的实际场景中,我想要动画的图像实际上是具有透明背景的文本(它不是浏览器支持的普通字体,所以我必须使用图像而不是纯文本)。在我看来“destination-out”模式已经足够好了,我不太了解“xor”的优势(正如你所说,它是让canvas元素透明,但默认情况下它不是透明的吗? )。
  • @shenkwen 我猜这是口味问题。你也可以出境。有许多途径可以达到相同的结果。某些操作可能比其他操作具有性能优势,但请针对您的场景进行测试(异或反转 alpha)
【解决方案2】:

使用上下文复合属性通过渐变进行遮罩。创建一个与图像或显示画布大小相同的屏幕外画布,以最小者为准。

为每一帧创建带有适当色标的渐变(CSS 颜色格式rgba(red, green, blue, alpha))以设置 alpha 值。

清除屏幕外画布

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

然后将屏幕画布的合成值设置为

ctxOffScreen.globalCompositeOperation = "source-over";

将图像渲染到上面

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

然后将comp设置为

ctxOffScreen.globalCompositeOperation = "destination-in";

这将匹配已绘制的像素,其 alpha 值与您接下来绘制的每个像素(渐变蒙版)中的 alpha 值相同

然后将填充样式设置为您创建的渐变并在顶部绘制一个矩形

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

然后将屏幕外的画布渲染到屏幕上的画布

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

如果你使用

ctxOffScreen.globalCompositeOperation = "destination-out";

您将反转使用渐变创建的蒙版,而不是 "destination-in"

【讨论】:

    【解决方案3】:

    作为canvas 的替代品,您可以使用svg。 CSS 渐变是不可动画的,但 svg 的其他属性是,所以你可以找到一些创造性的方法来动画渐变。

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

    您可能可以直接为offset 属性设置动画,我还没有测试过。

    【讨论】:

    • “CSS 渐变不是动画”通过 css&lt;xxxGradient&gt; 中的所有 specific attributes,除了 xlink:href 之一是可动画的。所以 SMIL 和 js 可以为这些设置动画。您还可以为&lt;stop&gt; 元素的属性设置动画。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-27
    • 1970-01-01
    • 2020-11-08
    • 1970-01-01
    • 2021-10-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多