2017-02-09 11 views
0

私は画像や動画をクリップする必要がある状況があります。画像や動画は重なり合う必要があります。これはもともとSVGで試してみましたが、いろいろな理由でうまくいきませんでしたので、キャンバスでやっています。キャンバスを通って動画をクリップする最も効率的な方法は何ですか

これは画像でうまくいきましたが、ビデオになると、ブラウザは約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()を使用しようとしましたが、あまり役に立ちません。

その他の最適化はありますか? (ビデオのサイズを保存します)。

let imageFrames = function() { 
 
\t let defaults = { 
 
\t \t wedgeHeight: 50 
 
\t }; 
 
\t return { 
 
\t \t defaults: defaults, 
 

 
\t \t //all wedges draw paths clockwise: top right, bottom right, bottom left, top left 
 
\t \t wedgeTop: (context, wedgeHeight = defaults.wedgeHeight) => { 
 
\t \t \t var wedge = new Path2D(); 
 

 
\t \t \t wedge.moveTo(this.dimensions.width, 0); 
 
\t \t \t wedge.lineTo(this.dimensions.width, this.dimensions.height); 
 
\t \t \t wedge.lineTo(0, this.dimensions.height); 
 
\t \t \t wedge.lineTo(0, wedgeHeight); 
 
\t \t \t wedge.closePath(); 
 
\t \t \t context.clip(wedge); 
 
\t \t }, 
 

 
\t \t wedgeTopReverse: (context, wedgeHeight = defaults.wedgeHeight) => { 
 
\t \t \t var wedge = new Path2D(); 
 

 
\t \t \t wedge.moveTo(this.dimensions.width, wedgeHeight); 
 
\t \t \t wedge.lineTo(this.dimensions.width, this.dimensions.height); 
 
\t \t \t wedge.lineTo(0, this.dimensions.height); 
 
\t \t \t wedge.lineTo(0, 0); 
 
\t \t \t wedge.closePath(); 
 
\t \t \t context.clip(wedge); 
 

 
\t \t }, 
 

 
\t \t wedgeBottom: (context, wedgeHeight = defaults.wedgeHeight) => { 
 
\t \t \t var wedge = new Path2D(); 
 

 
\t \t \t wedge.moveTo(this.dimensions.width, 0); 
 
\t \t \t wedge.lineTo(this.dimensions.width, this.dimensions.height - wedgeHeight); 
 
\t \t \t wedge.lineTo(0, this.dimensions.height); 
 
\t \t \t wedge.lineTo(0,0); 
 
\t \t \t wedge.closePath(); 
 
\t \t \t context.clip(wedge); 
 
\t \t }, 
 

 
\t \t wedgeBottomReverse: (context, wedgeHeight = defaults.wedgeHeight) => { 
 
\t \t \t var wedge = new Path2D(); 
 

 
\t \t \t wedge.moveTo(this.dimensions.width, 0); 
 
\t \t \t wedge.lineTo(this.dimensions.width, this.dimensions.height); 
 
\t \t \t wedge.lineto(0, this.dimensions.height - wedgeHeight); 
 
\t \t \t wedge.lineTo(0, 0); 
 
\t \t \t wedge.closePath(); 
 
\t \t \t context.clip(wedge); 
 
\t \t } 
 
\t }; 
 
}; 
 

 
class ImageCanvasModule { 
 
\t constructor(module) { 
 
\t \t this.module = module; 
 
\t \t this.imageFrames = imageFrames.call(this); 
 

 
\t \t if(this.isVideo) { 
 
\t \t \t /*drawFrame has a check where it'll only draw on reqAnimationFrame if video.paused === false, 
 
\t \t \t so we need to fire drawFrame on both events because that boolean will be false when it's paused, thus cancelling the animation frame 
 
\t \t \t */ 
 
\t \t \t this.media.addEventListener('play',()=>{ 
 
\t \t \t \t this.drawOnCanvas(); 
 
\t \t \t }); 
 

 
\t \t \t this.media.addEventListener('pause',()=> { 
 
\t \t \t \t this.drawOnCanvas(); 
 
\t \t \t }); 
 
\t \t } 
 
\t } 
 

 
\t get isPicture() { 
 
\t \t return (this.module.nodeName === 'PICTURE'); 
 
\t } 
 

 
\t get isVideo() { 
 
\t \t return (this.module.nodeName === 'VIDEO'); 
 
\t } 
 

 
\t get media() { 
 
\t \t return this.isPicture ? this.module.querySelector('img') : this.module; 
 
\t } 
 

 
\t get context() { 
 
\t \t return this.canvas.getContext('2d'); 
 
\t } 
 

 
\t get dimensions() { 
 
\t \t return { 
 
\t \t \t width: this.module.offsetWidth, 
 
\t \t \t height: this.module.offsetHeight 
 
\t \t }; 
 
\t } 
 

 
\t createCanvas() { 
 
\t \t let canvas = document.createElement('canvas'); 
 

 
\t \t this.module.parentNode.insertBefore(canvas, this.module.nextSibling); 
 
\t \t canvas.className = this.module.className; 
 

 
\t \t this.canvas = canvas; 
 

 
\t \t this.createBackContext(); 
 
\t } 
 

 
\t createBackContext() { 
 
\t \t this.backCanvas = document.createElement('canvas'); 
 
\t \t this.backContext = this.backCanvas.getContext('2d'); 
 

 
\t \t this.backCanvas.width = this.dimensions.width; 
 
\t \t this.backCanvas.height = this.backCanvas.height; 
 
\t } 
 

 
\t sizeCanvas() { 
 
\t \t this.canvas.height = this.dimensions.height; 
 
\t \t this.canvas.width = this.dimensions.width; 
 

 
\t \t this.backCanvas.height = this.dimensions.height; 
 
\t \t this.backCanvas.width = this.dimensions.width; 
 
\t } 
 

 
\t drawFrame() { 
 
\t \t if (this.isVideo && this.media.paused) return false; 
 

 
\t \t let x = 0; 
 
\t \t let width = this.media.offsetWidth; 
 
\t \t let y = 0; 
 
\t \t 
 
\t \t this.imageFrames[this.module.dataset.imageFrame](this.backContext); 
 
\t \t this.backContext.drawImage(this.media, x, y, width, this.canvas.height); 
 

 
\t \t this.context.drawImage(this.backCanvas, 0, 0); 
 

 
\t \t if (this.isVideo) { 
 
\t \t \t window.requestAnimationFrame(()=>{ 
 
\t \t \t \t this.drawFrame(); 
 
\t \t \t }); 
 
\t \t } 
 
\t } 
 

 
\t drawOnCanvas() { 
 
\t \t this.sizeCanvas(); 
 
\t \t this.drawFrame(); 
 
\t } 
 

 
\t hideOriginal() { 
 
\t \t //don't use display: none .... you can't get image dimensions when you do that. 
 
\t \t this.module.style.opacity = 0; 
 
\t } 
 
} 
 
console.clear(); 
 

 
window.addEventListener('DOMContentLoaded',()=> { 
 
\t var els = document.querySelectorAll('.canvasify'); 
 
\t var canvasified = []; 
 

 
\t for (el of els) { 
 
\t \t if (el.dataset.imageFrame) { 
 
\t \t \t let imageModule = new ImageCanvasModule(el); 
 
\t \t \t imageModule.createCanvas(); 
 
\t \t \t imageModule.drawOnCanvas(); 
 
\t \t \t imageModule.hideOriginal(); 
 
\t \t \t canvasified.push(imageModule); 
 
\t \t } 
 

 
\t } 
 
\t console.log(canvasified); 
 
});
body { 
 
\t background-color: #333; 
 
} 
 

 
.container { 
 
\t height: 600px; 
 
\t width: 100%; 
 
\t position: relative; 
 
\t display: flex; 
 
\t flex-direction: column; 
 
\t justify-content: center; 
 
} 
 
.container + .container { 
 
\t margin-top: -150px; 
 
} 
 
.canvasify { 
 
\t position:absolute; 
 
\t top: 0; 
 
\t left: 0; 
 
\t right: 0; 
 
\t bottom: 0; 
 
\t width: 100%; 
 
\t z-index: -1; 
 
} 
 
video { 
 
\t width: 100% 
 
} 
 

 
h1 { 
 
\t font-size: 2em; 
 
\t color: #ddd; 
 
}
<div class="container"> 
 
\t <img class="canvasify" data-image-frame="wedgeTop" src="http://placekitten.com/1280/500" /> 
 
\t <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> 
 
\t <h1>video with a clipped top that overlaps the image above</h1> 
 
</div>

問題がcodepen(このコードを実行している他のページ)が非常に遅いということです。何が最適化されていないか、または間違って使用されていますか?どのように他の人のコードに私のコードを比較することから、

The desired effect is an image in one container, a video in another, and they're both cropped

+0

「オーバーラップ」とはどういう意味ですか?何が問題なの? – guest271314

+0

ページが極端に遅くなるという問題があります。 「オーバーラップ」とは、ビデオの画像をクリップし、その画像の上に重なる画像を重ねることを意味します。ビデオ/画像は長方形ではなく、角度で切り取ったように見えます。 – paceaux

+0

まだありません。私はあなたがcodepenで見るものを正確に探しています。 2つの別々の容器。 1つは画像を含むかもしれない。もう1つはビデオを含むかもしれない。各コンテナはビデオまたはイメージのいずれかを持ち、ある角度で「クリップ」されます。 HTMLのようなテキストがイメージ/ビデオの上に座ります。したがって、1つのコンテナ内の画像の下端が、ビデオであるかもしれないものの上端の下に視覚的に表示されます。 私はそれを行う方法については助けが必要ではありません(私はすでにそれを行いました。)ページのパフォーマンスを向上させる助けが必要です。 – paceaux

答えて

1

は、このような状況で働いていた、私は欠陥は、私が実際にキャンバスにビデオから画像を描画するために使用していた私のdrawFrame()方法であることを発見しました。

二つの基本的な問題がありました:

  1. requestAnimationFrameの()60fpsの約動作し、これは30を超えないビデオが、
  2. 必要とされているので、私は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); 
     } 

    })(); 
} 

問題1のみ他のすべての画像を描画するtoggle変数を使用することによって解決されるように、新たなdrawFrame方法に見えることができますループが実行される時間。

問題2は、画像をループ外でトリミングすることで解決されます。

この2つの変更は、ページ上の他の要素がユーザーを読み込んでアニメーション化して返答する方法に顕著な違いをもたらしました。

今は明らかですが、ビデオの1フレームごとにクリップすると、キャンバスをクリッピングするよりもかなりコストがかかります。

コード例が問題を見つけるのに役立つユーザーK3Nに大きな感謝をします。

関連する問題