答案 0 :(得分:1)
是的,这是可能的。在这里你是如何实现这一目标的......
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var x = 4,
y = 4,
speed = 1,
isBottom = false;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#07C';
ctx.lineCap = 'round';
ctx.shadowBlur = 18;
ctx.shadowColor = "#07C";
ctx.fillRect(x, y, 210, 10);
if (!isBottom && y < canvas.height - 14) y += speed;
else if (y === canvas.height - 14) isBottom = true;
if (isBottom && y > 4) y -= speed;
else if (y === 4) isBottom = false;
requestAnimationFrame(draw);
}
draw();
&#13;
body{margin:0;overflow:hidden}canvas{border:1px solid #d3d3d3}
&#13;
<canvas id="canvas" width="218" height="218"></canvas>
&#13;
答案 1 :(得分:1)
使用HTML5画布和2D API
实现以下动画发光交叉渐变到背景FX图片归属可以在本答案底部的演示中视频的开始和结束标题/学分中找到
可以使用掩码实现FX,以掩盖您不想看到的部分视频。
将ctx.globalCompositeoperation
设置为“目标输出”将删除您渲染的所有现有像素,直到您使用ctx.globalCompositeoperation = "source-over";
关闭复合操作(默认设置)您可以使用以下方式获取反向FX “目的地在”
如果您想在分割时显示背景,可以使用补偿模式"destination-atop"
在蒙版视频上绘制背景。背景可以是空白的黑色(在下面的代码中)或另一个视频源,以创建一种交叉渐变FX。
注意如果您将背景保持透明,那么发光条将无法正确显示画布背景上的较亮FX(DOM背景)
注意两个代码段都假设视频已经渲染到画布上。
// direction is the direction of the sweeping FX
// FXMix is the position of the mask normalised for canvas width or height
// FXMix has a range of 0-1 inclusive with 0 being top or left and
// 1 being bottom or right.
// negate if true reverses the FX mask.
// Assuming that canvas and ctx (2D context) are in scope of this function
function mask(direction, FXMix, negate){
ctx.globalCompositeOperation = negate ? "destination-out" : "destination-in";
ctx.fillStyle = "black";
ctx.globalAlpha = 1;
if(direction === "Vertical"){
ctx.fillRect(0, 0, canvas.width, canvas.height * FXMix);
}else if(direction === "Horizontal"){
ctx.fillRect(0, 0, canvas.width * FXMix, canvas.height);
}
ctx.globalCompositeOperation = "destination-atop";
// assuming background is black. But you could use anything including a second video
// using "destination-atop" only draws on transparent pixels.
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "source-over";
}
然后可以使用渐变和fillRect在顶部绘制扫描光FX。为了给发光提供更多的身体,我画两次,一次没有任何合成FX,第二次使用ctx.globalCompositeOperation = "lighter"
稍微大一点。而不是必须移动渐变我使用setTransform来定位渐变。如果您不想更改发光条颜色,则可以在节省一点CPU时间后创建渐变。
// RGB is an array of channel values for R,G,B in the range 0-255
// NOTE RGB must be integer values. Please floor them befor passing
// direction is the direction of the sweeping FX
// FXMix is the position of the mask normalised for canvas width or height
// FXMix has a range of 0-1 inclusive with 0 being top or left and
// 1 being bottom or right.
// negate if true reverses the FX mask.
// Assuming that canvas and ctx (2D context) are in scope of this function
// size is size of bar as a fraction pf canvas width or height.
// A good value is 0.1
function glowBar(RGB, direction, FXMix, size){
var grad;
var col = "rgba("+RGB[0]+","+RGB[1]+","+RGB[2]+",";
if(direction === "Vertical"){
grad = ctx.createLinearGradient(0,0,0,canvas.height * size);
}else if(direction === "Horizontal"){
grad = ctx.createLinearGradient(0,0,canvas.width * size,0);
}
grad.addColorStop(0.0, col + "0.0)");
grad.addColorStop(0.3, col + "0.8)");
grad.addColorStop(0.4, col + "1.0)");
grad.addColorStop(0.6, col + "1.0)");
grad.addColorStop(0.7, col + "0.8)");
grad.addColorStop(1.0, col + "0.0)");
ctx.fillStyle = grad;
if(direction === "Vertical"){
// draw first time with standard comp
ctx.setTransform(1,0,0,1,0,canvas.height * FXMix - canvas.height * size * 0.5);
ctx.fillRect(0, 0, canvas.width, canvas.height * size);
// draw second time slightly bigger and with lighten to get a glow FX
ctx.setTransform(1,0,0,1.1,0,canvas.height * FXMix -canvas.height * size * 0.5 * 1.1 );
ctx.globalCompositeOperation = "lighter";
ctx.fillRect(0, 0, canvas.width, canvas.height *size);
}else if(direction === "Horizontal"){
ctx.setTransform(1,0,0,1,canvas.width * FXMix -canvas.width * size * 0.5, 0);
ctx.fillRect(0, 0, canvas.width * size, canvas.height);
ctx.setTransform(1.1,0,0,1,canvas.width * FXMix -canvas.width * size * 0.5 * 1.1, 0);
ctx.globalCompositeOperation = "lighter";
ctx.fillRect(0, 0, canvas.width * size, canvas.height);
}
ctx.globalCompositeOperation = "source-over";
ctx.setTransform(1,0,0,1,0,0);
}
以下演示是我在Fade from black & white to color答案中使用的现有演示的更新。如果您将鼠标悬停在视频右侧(已加载),我可以在FX列表中添加2个FX )V扫描仪和H扫描仪将设置动画,但如果将鼠标悬停在视频底部,可以通过混合滑块进行手动控制。
FX只是对上述两个片段函数mask
和glowBar
进行了轻微修改。
视频归属可以在视频的开头和结尾标题/信用中找到(由作者指定)
//==========================================================================
// start canvas animation after all the code below has been parsed and executed
setTimeout(()=>requestAnimationFrame(updateCanvas),0);
//==========================================================================
// UI variables
var showFXName = 0;
var cursor = "default";
var overUI = false;
var sliderAlpha = 1;
var listAlpha = 1;
var dragging = false;
var listWidth = null;
var playClick = false;
//==========================================================================
// Media setup
var mediaSource = "http://video.webmfiles.org/big-buck-bunny_trailer.webm";
var mediaSource = "http://upload.wikimedia.org/wikipedia/commons/7/79/Big_Buck_Bunny_small.ogv";
var muted = true;
var canvas = document.getElementById("myCanvas"); // get the canvas from the page
var ctx = canvas.getContext("2d");
var videoContainer; // object to hold video and associated info
var video = document.createElement("video"); // create a video element
video.src = mediaSource;
// the video will now begin to load.
// As some additional info is needed we will place the video in a
// containing object for convenience
video.autoPlay = false; // ensure that the video does not auto play
video.loop = true; // set the video to loop.
video.muted = muted;
videoContainer = { // we will add properties as needed
video : video,
ready : false,
};
// To handle errors. This is not part of the example at the moment. Just fixing for Edge that did not like the ogv format video
video.onerror = function(e){
document.body.removeChild(canvas);
document.body.innerHTML += "<h2>There is a problem loading the video</h2><br>";
document.body.innerHTML += "Users of IE9+ , the browser does not support WebM videos used by this demo";
document.body.innerHTML += "<br><a href='https://tools.google.com/dlpage/webmmf/'> Download IE9+ WebM support</a> from tools.google.com<br> this includes Edge and Windows 10";
}
video.oncanplay = readyToPlayVideo; // set the event to the play function that
// can be found below
//==========================================================================
var FXMix = 1;
var FX = {}
var FXList = [];
var currentFX = "";
var animateFXMix = false; // if true thr FXMix is animated over time. This flag is cleared to false once a frame
//==========================================================================
// Mix function are in this section
function addOverlay(type){
if(FXMix > 0){
ctx.globalCompositeOperation = type;
ctx.globalAlpha = FXMix;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
}
}
function addMix(type,video){
if(FXMix > 0){
ctx.globalCompositeOperation = type;
ctx.globalAlpha = FXMix;
ctx.drawImage(video,0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
}
}
function fill(style){ // draws a rectangle over the canvas using style as the colour
ctx.globalAlpha = FXMix;
ctx.fillStyle = style;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 1;
}
function mask(direction){
ctx.globalCompositeOperation = "destination-in";
ctx.fillStyle = "black";
ctx.globalAlpha = 1;
if(direction === "Vertical"){
ctx.fillRect(0, 0, canvas.width, canvas.height * FXMix);
}else if(direction === "Horizontal"){
ctx.fillRect(0, 0, canvas.width * FXMix, canvas.height);
}
ctx.globalCompositeOperation = "destination-atop";
// assuming bacground is black. But you could use anything including a second video
// using "destination-atop" only draws on transparent pixels.
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "source-over";
}
function glowBar(RGB, direction){ // RGB is array of channel values 0-255
var grad;
var col = "rgba("+RGB[0]+","+RGB[1]+","+RGB[2]+",";
if(direction === "Vertical"){
grad = ctx.createLinearGradient(0,0,0,canvas.height * 0.1);
}else if(direction === "Horizontal"){
grad = ctx.createLinearGradient(0,0,canvas.width * 0.1,0);
}
grad.addColorStop(0.0, col + "0.0)");
grad.addColorStop(0.3, col + "0.8)");
grad.addColorStop(0.4, col + "1.0)");
grad.addColorStop(0.6, col + "1.0)");
grad.addColorStop(0.7, col + "0.8)");
grad.addColorStop(1.0, col + "0.0)");
ctx.fillStyle = grad;
if(direction === "Vertical"){
ctx.setTransform(1,0,0,1,0,canvas.height * FXMix - canvas.height * 0.05);
ctx.fillRect(0, 0, canvas.width, canvas.height * 0.1);
ctx.setTransform(1,0,0,1.1,0,canvas.height * FXMix -canvas.height * 0.05 * 1.1 );
ctx.globalCompositeOperation = "lighter";
ctx.fillRect(0, 0, canvas.width, canvas.height * 0.1);
}else if(direction === "Horizontal"){
ctx.setTransform(1,0,0,1,canvas.width * FXMix -canvas.width * 0.05, 0);
ctx.fillRect(0, 0, canvas.width * 0.1, canvas.height);
ctx.setTransform(1.1,0,0,1,canvas.width * FXMix -canvas.width * 0.05 * 1.1, 0);
ctx.globalCompositeOperation = "lighter";
ctx.fillRect(0, 0, canvas.width * 0.1, canvas.height);
}
ctx.globalCompositeOperation = "source-over";
ctx.setTransform(1,0,0,1,0,0);
}
function addFX(name, func) {
FXList.push(name);
FX[name] = func;
currentFX = name;
}
//==========================================================================
// avialable composite operations
// multiply,screen,overlay,color-dodge,color-burn,hard-light,soft-light,difference,
// exclusion,hue,saturation,color,luminosity
addFX("V scanner", (vid)=>{ animateFXMix = true; mask("Vertical"); glowBar([100,200,255], "Vertical") })
addFX("H scanner", (vid)=>{ animateFXMix = true; mask("Horizontal"); glowBar([100,255,200], "Horizontal") })
addFX("Ligher", (vid)=>{ addMix("lighter",vid) } );
addFX("Ligher+", (vid)=>{ addMix("lighter", vid); addMix("lighter",vid); addMix("lighter", vid) } );
addFX("Darken", (vid)=>{ addMix("multiply", vid) } );
addFX("Darken+", (vid)=>{ addMix("multiply", vid); addMix("multiply",vid); addMix("multiply", vid) } );
addFX("Saturate", () =>{ ctx.fillStyle = "#F00"; addOverlay("saturation") });
addFX("Negative", (vid)=>{ ctx.fillStyle = "#FFF"; addOverlay("difference") } );
addFX("Sepia", (vid)=>{ fill("#F94"); addMix("luminosity", vid) } );
addFX("BlackWhite", (vid)=>{ ctx.fillStyle = "#888"; addOverlay("color") } );
addFX("B&W Negative",(vid)=>{ ctx.fillStyle = "#FFF"; addOverlay("difference"); ctx.fillStyle = "#888"; addOverlay("color") } );
addFX("B&W Lighten", (vid)=>{ addMix("lighter", vid); ctx.fillStyle = "#888"; addOverlay("color") } );
addFX("None", () =>{});
// end of FX mixing
//==========================================================================
function readyToPlayVideo(event){ // this is a referance to the video
// the video may not match the canvas size so find a scale to fit
videoContainer.scale = Math.min(
canvas.width / this.videoWidth,
canvas.height / this.videoHeight);
videoContainer.ready = true;
// the video can be played so hand it off to the display function
// add instruction
document.getElementById("playPause").textContent = "Click video to play/pause.";
document.querySelector(".mute").textContent = "Mute";
}
function updateCanvas(time){
ctx.fillStyle = "#222";
ctx.fillRect(0,0,canvas.width,canvas.height)
// only draw if loaded and ready
if(videoContainer !== undefined && videoContainer.ready){
// find the top left of the video on the canvas
video.muted = muted;
var scale = videoContainer.scale;
var vidH = videoContainer.video.videoHeight;
var vidW = videoContainer.video.videoWidth;
var top = canvas.height / 2 - (vidH /2 ) * scale;
var left = canvas.width / 2 - (vidW /2 ) * scale;
// now just draw the video the correct size
ctx.drawImage(videoContainer.video, left, top, vidW * scale, vidH * scale);
FX[currentFX](videoContainer.video);
if(videoContainer.video.paused){ // if not playing show the paused screen
drawPayIcon();
}
overUI = false;
cursor = "default";
drawSlider();
drawList();
if(mouse.over){
if(!overUI){
if((mouse.button&1)===1){ // bit field
playClick = true;
}
if((mouse.button&1)===0 && playClick){ // bit field
playClick = false;
playPauseClick();
}
cursor = "pointer";
}
}
if(showFXName > 0){
showFXName = Math.max(0,showFXName - 0.05);
ctx.globalAlpha = Math.min(1,showFXName);
ctx.font = "32px Arial";
ctx.textAlign = "center";
ctx.textbaseLine = "middle";
ctx.fillStyle = "white";
ctx.strokeStyle = "black";
ctx.lineJoin = "round"
ctx.strokeText(currentFX,canvas.width/2,canvas.height/2);
ctx.fillText(currentFX,canvas.width/2,canvas.height/2);
ctx.globalAlpha = 1;
}
canvas.style.cursor = cursor;
if(animateFXMix){
FXMix = Math.sin(time / 400) * 0.5 + 0.5;
FXMix = FXMix < 0 ? 0 : FXMix > 1 ? 1 : FXMix; // Clamp to 0-1 or may get flicker on some FX
animateFXMix = false;
}
}else{
drawLoadingAnim(time);
}
// all done for display
// request the next frame in 1/60th of a second
requestAnimationFrame(updateCanvas);
}
function getMaxListWidth(){
ctx.font = "12px arial";
FXList.forEach(text => {listWidth = Math.max(listWidth,ctx.measureText(text).width)})
}
function drawList(){
if(listWidth === null){
getMaxListWidth();
listWidth += 10;
}
if(!overUI && mouse.over && mouse.x > canvas.width - listWidth){
listAlpha = 1;
overUI = true;
}else{
listAlpha = Math.max(0,listAlpha - 0.05);
}
if(listAlpha > 0){
ctx.font = "12px arial";
var textH = 14;
var border = 10;
ctx.textAlign = "right";
ctx.textBaseline = "middle";
ctx.globalAlpha = listAlpha;
ctx.fillStyle = "black";
ctx.strokeStyle = "white";
var len = FXList.length;
var h = len * textH;
var y = canvas.height / 2 - h/2;
var x = canvas.width - border * 2;
ctx.fillRect(x - listWidth,y - border, listWidth+border,h + border );
ctx.strokeRect(x - listWidth,y - border, listWidth + border,h + border );
ctx.fillStyle = "white"
for(var i = 0; i < len; i ++){
var yy = y + i * textH;
if(FXList[i] === currentFX){
ctx.fillStyle = "#0FF";
ctx.fillText(FXList[i],x,yy);
ctx.fillStyle = "white"
}else
if(mouse.x > canvas.width - listWidth && mouse.y > yy - textH/2 && mouse.y < yy + textH /2){
ctx.fillStyle = "#0F0";
ctx.fillText(FXList[i],x,yy);
ctx.fillStyle = "white"
cursor = "pointer";
if((mouse.button & 1) === 1){
currentFX =FXList[i];
showFXName = 4;
}
}else{
ctx.fillText(FXList[i],x,yy);
}
}
ctx.globalAlpha = 1;
}
}
function drawSlider(){
if(currentFX === "None"){
sliderAlpha = 0;
return;
}
if(dragging){
animateFXMix = false; // dont animate if slider being dragged
}
var cw = canvas.width;
var ch = canvas.height;
var handle = 5;
var inset = 10
var x = inset;
var w = cw - inset*2;
var h = 20;
var y = ch - inset - h;
var pos = FXMix * w + x;;
if(mouse.y > y - h* 2){
cursor = "e-resize";
overUI = true;
if((mouse.button&1) && !dragging){ // bit field
dragging = true;
}
}else{
cursor = "pointer";
}
if(dragging){
overUI = true;
cursor = "e-resize";
sliderAlpha = 1;
pos = mouse.x - x;
FXMix = Math.min(1,Math.max(0,pos / w));
if( (mouse.button&1) === 0 ){ //bit field
dragging = false;
}
}
if(!dragging && mouse.y > y-h*2 && mouse.over){
sliderAlpha = 1;
}else{
if(sliderAlpha > 0){
sliderAlpha = Math.max(0,sliderAlpha- 0.05);
}
}
if(sliderAlpha === 0){
return;
}
ctx.globalAlpha = sliderAlpha;
ctx.font = "18px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
var amount = FXMix;
ctx.fillStyle = "black";
ctx.strokeStyle = "white";
ctx.fillRect(x,y,w,h);
ctx.strokeRect(x,y,w,h);
ctx.fillStyle = "white";
ctx.fillText(currentFX + " "+ (FXMix * 100).toFixed(0)+"%",w/2,y + h / 2);
pos = amount * w + x;
ctx.fillStyle = "white";
ctx.strokeStyle = "black";
ctx.fillRect(pos-handle*2,y-handle,handle* 4,h + handle * 2);
ctx.strokeRect(pos-handle*2,y-handle,handle* 4,h + handle * 2);
ctx.strokeRect(pos-1,y-handle * 0.5,2,h + handle);
ctx.globalAlpha = 1;
}
function drawPayIcon(){
ctx.fillStyle = "#DDD"; // colour of play icon
ctx.globalAlpha = 0.75; // partly transparent
ctx.beginPath(); // create the path for the icon
var size = (canvas.height / 2) * 0.5; // the size of the icon
ctx.moveTo(canvas.width/2 + size/2, canvas.height / 2); // start at the pointy end
ctx.lineTo(canvas.width/2 - size/2, canvas.height / 2 + size);
ctx.lineTo(canvas.width/2 - size/2, canvas.height / 2 - size);
ctx.closePath();
ctx.fill();
ctx.globalAlpha = 1; // restore alpha
}
function drawLoadingAnim(time){
ctx.strokeStyle = "#8DF"; // colour of play icon
ctx.lineCap = "round";
ctx.globalAlpha = 0.75; // partly transparent
ctx.lineWidth = 6;
ctx.beginPath(); // create the path for the icon
var size = (canvas.height / 2) * 0.5; // the size of the icon
ctx.arc(canvas.width / 2 , canvas.height / 2, size, time / 100, time / 100 + 2);
ctx.stroke();
ctx.lineWidth = 1;
ctx.globalAlpha = 1; // restore alpha
}
mouse = (function(){
var mouse = {
x : 0, y : 0, w : 0,
button : 0,
over : false,
bm : [1, 2, 4, 6, 5, 3],
active : false,
bounds : null,
border : {top : 10, left : 10},
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,contextmenu".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.clientX - m.bounds.left - m.border.left;
m.y = e.clientY - m.bounds.top - m.border.top;
if (t === "mousedown") {
m.button |= m.bm[e.which-1];
} else if (t === "mouseup") {
m.button &= m.bm[e.which + 2];
}else if (t === "mouseout") {
m.button = 0;
m.over = false;
}else if (t === "mouseover") {
m.over = true;
}
e.preventDefault();
}
m.start = function (element) {
m.element = element;
m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
m.active = true;
//m.border.top = Number(element.style.borderTopWidth.replace(/[a-zA-Z]/g,""));
//m.border.left = Number(element.style.borderLeftWidth.replace(/[a-zA-Z]/g,""));
}
m.remove = function () {
if (m.element !== undefined) {
m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
m.active = false;
m.element = undefined;
}
}
return mouse;
})();
function playPauseClick(){
if(videoContainer !== undefined && videoContainer.ready){
if(videoContainer.video.paused){
videoContainer.video.play();
}else{
videoContainer.video.pause();
}
}
}
function videoMute(){
muted = !muted;
if(muted){
document.querySelector(".mute").textContent = "Mute";
}else{
document.querySelector(".mute").textContent= "Sound on";
}
}
// register the event
//canvas.addEventListener("click",playPauseClick);
document.querySelector(".mute").addEventListener("click",videoMute)
setTimeout(()=>{mouse.start(canvas)},100);
body {
font :14px arial;
text-align : center;
background : #36A;
}
h2 {
color : white;
}
canvas {
border : 10px white solid;
cursor : pointer;
}
a {
color : #F93;
}
.mute {
cursor : pointer;
display: initial;
}
<h2>Simple video FX via canvas "globalCompositeOperation"</h2>
<p>This example show how to use the 2d context "globalCompositeOperation" property to create a variety of FX. Video may take a few moment to load.
</p>
<p>Play pause video with click. Move to bottom of video to see FX mix slider (Not available if filter None). Move to right to get filter selection and select the filter example. V and H scanner FX are animated. You can use the slider to move the glowbar position manualy. Happy filtering</p>
<canvas id="myCanvas" width = "532" height ="300" ></canvas><br>
<h3><div id = "playPause">Loading content.</div></h3>
<div class="mute"></div><br>