动画:使图像从一侧到另一侧渐变出现(不透明度)(所谓的 "soft-wipe" 效果)
Animation: Making an image gradiently appear from side to side (in opacity)(so called "soft-wipe" effect)
抱歉,如果标题令人困惑。我已经尽力写好了,如果你想明白我在问什么,请随时在评论中提出更好的标题。
我正在尝试制作的动画可以很容易地由视频编辑器完成,但在我看来 CSS/JS 并不那么容易:首先,我不是在谈论在图像中滑动,图像根本不动。我希望它从一边到另一边出现,但以渐变透明的方式出现。假设图片上有一个渐变不透明度遮罩,让它的左端不透明度为1,右端不透明度为0。当这个遮罩从左向右移动时,就是我要实现的动画。
我可以将图像分割成几块,并控制每块的不透明度,必须有一定数量的块才能使整个动画流畅和吸引人。
我想的另一种方法是使用 canvas,您可以按像素操作图像,正如 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插件可以实现这个?
使用上下文复合 属性 通过渐变进行遮罩。创建一个屏幕外 canvas 与图像或显示器大小 canvas 相同的大小,以最小者为准。
为每一帧创建具有适当色标(CSS 颜色格式 rgba(red, green, blue, alpha)
)的渐变以设置 alpha 值。
清除关闭屏幕canvas
ctxOffScreen.clearRect( 0, 0, ctxOffScreen.canvas.width, ctxOffScreen.canvas.height);
然后将屏幕 canvas 的复合值设置为
ctxOffScreen.globalCompositeOperation = "source-over";
将图像渲染到它上面
ctxOffScreen.drawImage(image, 0, 0, ctxOffScreen.canvas.width, ctxOffScreen.canvas.height);
然后将补偿设置为
ctxOffScreen.globalCompositeOperation = "destination-in";
这将匹配已经绘制的像素,其 alpha 值与您接下来绘制的每个(渐变蒙版)中的相同 alpha 值
然后将填充样式设置为您创建的渐变,并在顶部绘制一个矩形
ctxOffScreen.fillStyle = gradient;
ctxOffScreen.fillRect( 0, 0, ctxOffScreen.canvas.width, ctxOffScreen.canvas.height);
然后将屏幕外 canvas 渲染到屏幕上 canvas
ctx.drawImage(ctxOffScreen.canvas, 0, 0);
如果你使用
ctxOffScreen.globalCompositeOperation = "destination-out";
而不是 "destination-in"
您将反转您使用渐变创建的蒙版。
作为 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
属性 设置动画,我还没有测试过。
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
在视频行业,我们称之为软擦(AKA 软边擦),制作起来并不复杂。
您只需要一个可以使用线性渐变制作的 alpha 蒙版。然后使用具有上下文的翻译属性结合 xor 复合模式对其进行动画处理。
xor 模式的作用是根据绘制到它的 alpha 通道反转 alpha 通道。这样做的好处是 canvas 元素也变得透明,所以任何背景都可以透过。您可以保留默认排版。模式也会使背景变黑。
渐变是这样制作的(颜色值与 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;
(请参阅 了解如何通过创建平滑渐变来避免“亮线”伪像)。
现在创建一个函数,根据位置 t
绘制一个完整的框架,这是一个归一化值与 canvas 宽度相结合 - 请记住,我们需要使用双倍宽度:渐变 +梯度退出的空间 -
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}
<canvas width=658 height=325 id=c></canvas>
抱歉,如果标题令人困惑。我已经尽力写好了,如果你想明白我在问什么,请随时在评论中提出更好的标题。
我正在尝试制作的动画可以很容易地由视频编辑器完成,但在我看来 CSS/JS 并不那么容易:首先,我不是在谈论在图像中滑动,图像根本不动。我希望它从一边到另一边出现,但以渐变透明的方式出现。假设图片上有一个渐变不透明度遮罩,让它的左端不透明度为1,右端不透明度为0。当这个遮罩从左向右移动时,就是我要实现的动画。
我可以将图像分割成几块,并控制每块的不透明度,必须有一定数量的块才能使整个动画流畅和吸引人。
我想的另一种方法是使用 canvas,您可以按像素操作图像,正如 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插件可以实现这个?
使用上下文复合 属性 通过渐变进行遮罩。创建一个屏幕外 canvas 与图像或显示器大小 canvas 相同的大小,以最小者为准。
为每一帧创建具有适当色标(CSS 颜色格式 rgba(red, green, blue, alpha)
)的渐变以设置 alpha 值。
清除关闭屏幕canvas
ctxOffScreen.clearRect( 0, 0, ctxOffScreen.canvas.width, ctxOffScreen.canvas.height);
然后将屏幕 canvas 的复合值设置为
ctxOffScreen.globalCompositeOperation = "source-over";
将图像渲染到它上面
ctxOffScreen.drawImage(image, 0, 0, ctxOffScreen.canvas.width, ctxOffScreen.canvas.height);
然后将补偿设置为
ctxOffScreen.globalCompositeOperation = "destination-in";
这将匹配已经绘制的像素,其 alpha 值与您接下来绘制的每个(渐变蒙版)中的相同 alpha 值
然后将填充样式设置为您创建的渐变,并在顶部绘制一个矩形
ctxOffScreen.fillStyle = gradient;
ctxOffScreen.fillRect( 0, 0, ctxOffScreen.canvas.width, ctxOffScreen.canvas.height);
然后将屏幕外 canvas 渲染到屏幕上 canvas
ctx.drawImage(ctxOffScreen.canvas, 0, 0);
如果你使用
ctxOffScreen.globalCompositeOperation = "destination-out";
而不是 "destination-in"
您将反转您使用渐变创建的蒙版。
作为 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
属性 设置动画,我还没有测试过。
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
在视频行业,我们称之为软擦(AKA 软边擦),制作起来并不复杂。
您只需要一个可以使用线性渐变制作的 alpha 蒙版。然后使用具有上下文的翻译属性结合 xor 复合模式对其进行动画处理。
xor 模式的作用是根据绘制到它的 alpha 通道反转 alpha 通道。这样做的好处是 canvas 元素也变得透明,所以任何背景都可以透过。您可以保留默认排版。模式也会使背景变黑。
渐变是这样制作的(颜色值与 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;
(请参阅
现在创建一个函数,根据位置 t
绘制一个完整的框架,这是一个归一化值与 canvas 宽度相结合 - 请记住,我们需要使用双倍宽度:渐变 +梯度退出的空间 -
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}
<canvas width=658 height=325 id=c></canvas>