我遇到需要剪辑图片或视频的情况。图像或视频需要能够重叠。我们最初使用SVG尝试了这个,但由于各种原因,这并没有很好地解决,所以现在我们在Canvas中做了。
这对于图像效果很好,但是当涉及到视频时,浏览器在大约2分钟后几乎停止了。 (您从示例代码或链接中看到的内容是,我们也在暂停视频时暂停视频,而且视图中的标签不在视图中。)
这是一个链接:http://codepen.io/paceaux/pen/egLOeR
主要担心的是这种方法:
drawFrame () {
if (this.isVideo && this.media.paused) return false;
let x = 0;
let width = this.media.offsetWidth;
let y = 0;
this.imageFrames[this.module.dataset.imageFrame](this.backContext);
this.backContext.drawImage(this.media, x, y, width, this.canvas.height);
this.context.drawImage(this.backCanvas, 0, 0);
if (this.isVideo) {
window.requestAnimationFrame(()=>{
this.drawFrame();
});
}
}
您将观察到浏览器立即放慢速度。我不建议长时间查看该代码,因为各地的事情都会变得非常缓慢。
我正在使用"backCanvas" technique,但这似乎让事情变得更糟。
我还尝试使用Path2D()
来保存剪辑路径,但这似乎也没什么帮助。
wedgeTop: (context, wedgeHeight = defaults.wedgeHeight) => {
var wedge = new Path2D();
wedge.moveTo(this.dimensions.width, 0);
wedge.lineTo(this.dimensions.width, this.dimensions.height);
wedge.lineTo(0, this.dimensions.height);
wedge.lineTo(0, wedgeHeight);
wedge.closePath();
context.clip(wedge);
},
我还缺少任何其他优化措施吗? (除了视频的大小)。
let imageFrames = function () {
let defaults = {
wedgeHeight: 50
};
return {
defaults: defaults,
//all wedges draw paths clockwise: top right, bottom right, bottom left, top left
wedgeTop: (context, wedgeHeight = defaults.wedgeHeight) => {
var wedge = new Path2D();
wedge.moveTo(this.dimensions.width, 0);
wedge.lineTo(this.dimensions.width, this.dimensions.height);
wedge.lineTo(0, this.dimensions.height);
wedge.lineTo(0, wedgeHeight);
wedge.closePath();
context.clip(wedge);
},
wedgeTopReverse: (context, wedgeHeight = defaults.wedgeHeight) => {
var wedge = new Path2D();
wedge.moveTo(this.dimensions.width, wedgeHeight);
wedge.lineTo(this.dimensions.width, this.dimensions.height);
wedge.lineTo(0, this.dimensions.height);
wedge.lineTo(0, 0);
wedge.closePath();
context.clip(wedge);
},
wedgeBottom: (context, wedgeHeight = defaults.wedgeHeight) => {
var wedge = new Path2D();
wedge.moveTo(this.dimensions.width, 0);
wedge.lineTo(this.dimensions.width, this.dimensions.height - wedgeHeight);
wedge.lineTo(0, this.dimensions.height);
wedge.lineTo(0,0);
wedge.closePath();
context.clip(wedge);
},
wedgeBottomReverse: (context, wedgeHeight = defaults.wedgeHeight) => {
var wedge = new Path2D();
wedge.moveTo(this.dimensions.width, 0);
wedge.lineTo(this.dimensions.width, this.dimensions.height);
wedge.lineto(0, this.dimensions.height - wedgeHeight);
wedge.lineTo(0, 0);
wedge.closePath();
context.clip(wedge);
}
};
};
class ImageCanvasModule {
constructor(module) {
this.module = module;
this.imageFrames = imageFrames.call(this);
if(this.isVideo) {
/*drawFrame has a check where it'll only draw on reqAnimationFrame if video.paused === false,
so we need to fire drawFrame on both events because that boolean will be false when it's paused, thus cancelling the animation frame
*/
this.media.addEventListener('play', ()=>{
this.drawOnCanvas();
});
this.media.addEventListener('pause', ()=> {
this.drawOnCanvas();
});
}
}
get isPicture() {
return (this.module.nodeName === 'PICTURE');
}
get isVideo() {
return (this.module.nodeName === 'VIDEO');
}
get media() {
return this.isPicture ? this.module.querySelector('img') : this.module;
}
get context() {
return this.canvas.getContext('2d');
}
get dimensions() {
return {
width: this.module.offsetWidth,
height: this.module.offsetHeight
};
}
createCanvas () {
let canvas = document.createElement('canvas');
this.module.parentNode.insertBefore(canvas, this.module.nextSibling);
canvas.className = this.module.className;
this.canvas = canvas;
this.createBackContext();
}
createBackContext () {
this.backCanvas = document.createElement('canvas');
this.backContext = this.backCanvas.getContext('2d');
this.backCanvas.width = this.dimensions.width;
this.backCanvas.height = this.backCanvas.height;
}
sizeCanvas () {
this.canvas.height = this.dimensions.height;
this.canvas.width = this.dimensions.width;
this.backCanvas.height = this.dimensions.height;
this.backCanvas.width = this.dimensions.width;
}
drawFrame () {
if (this.isVideo && this.media.paused) return false;
let x = 0;
let width = this.media.offsetWidth;
let y = 0;
this.imageFrames[this.module.dataset.imageFrame](this.backContext);
this.backContext.drawImage(this.media, x, y, width, this.canvas.height);
this.context.drawImage(this.backCanvas, 0, 0);
if (this.isVideo) {
window.requestAnimationFrame(()=>{
this.drawFrame();
});
}
}
drawOnCanvas () {
this.sizeCanvas();
this.drawFrame();
}
hideOriginal () {
//don't use display: none .... you can't get image dimensions when you do that.
this.module.style.opacity = 0;
}
}
console.clear();
window.addEventListener('DOMContentLoaded', ()=> {
var els = document.querySelectorAll('.canvasify');
var canvasified = [];
for (el of els) {
if (el.dataset.imageFrame) {
let imageModule = new ImageCanvasModule(el);
imageModule.createCanvas();
imageModule.drawOnCanvas();
imageModule.hideOriginal();
canvasified.push(imageModule);
}
}
console.log(canvasified);
});

body {
background-color: #333;
}
.container {
height: 600px;
width: 100%;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
}
.container + .container {
margin-top: -150px;
}
.canvasify {
position:absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
z-index: -1;
}
video {
width: 100%
}
h1 {
font-size: 2em;
color: #ddd;
}

<div class="container">
<img class="canvasify" data-image-frame="wedgeTop" src="http://placekitten.com/1280/500" />
<h1>Kitty with a clipped top</h1>
</div>
<div class="container">
<video controls muted class="canvasify" loop autoplay data-image-frame="wedgeTop">
<source src="https://poc5.ssl.cdn.sdlmedia.com/web/635663565028367012PU.mp4">
</video>
<h1>video with a clipped top that overlaps the image above</h1>
</div>
&#13;
问题是codepen(以及运行此代码的其他页面)非常慢。我错过了什么优化,或使用不正确?
答案 0 :(得分:1)
从比较我的代码到其他人的代码如何在这种情况下工作,我发现这个缺陷是我用drawFrame()
方法实际将图像从视频绘制到画布中。
有两个基本问题:
drawFrame
的每个实例中绘制剪辑,我不需要这样做。您可以剪切画布一次,然后运行requestAnimationFrame
因此,新的drawFrame方法看起来像这样
drawFrame () {
if (this.isVideo && this.media.paused) return false;
this.imageFrames[this.module.dataset.imageFrame]();
var _this = this;
var toggle = false;
(function loop() {
toggle= !toggle;
if (toggle) {
let x = 0;
let width = _this.media.offsetWidth;
let y = 0;
_this.context.drawImage(_this.media, 0, 0, width, _this.canvas.height);
}
if (_this.isVideo) {
window.requestAnimationFrame(loop);
}
})();
}
通过使用toggle
变量仅在循环运行时每次绘制图像来解决问题1。
通过在循环外部裁剪图像来解决问题2。
这两项更改在页面上的其他元素如何加载,动画和响应用户方面产生了明显的差异。
现在看来显而易见,但剪切视频中的每一帧比剪切画布要昂贵得多。
非常感谢用户K3N,其代码示例帮助我找出问题所在。