2017-02-09 8 views
0

У меня есть ситуация, когда мне нужно обрезать изображения или видео. Изображения или видеоролики должны быть перекрыты. Мы попробовали это первоначально с помощью 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() { 
 
\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

Проблема в том, что страница работает очень медленно. «Overlaps» означает, что изображение из видео должно быть обрезано и перекрывать изображение, которое находится над ним. заставляя его выглядеть как видео/изображение не прямоугольное, а вырезанное под углами. – paceaux

+0

По-прежнему нет. Я ищу именно то, что вы видите в кодефене. Два отдельных контейнера. один может содержать изображение. Другой может содержать видео. Каждый контейнер будет иметь либо видео, либо изображение и будет «обрезано» под углом. Текст, похожий на HTML, будет сидеть над изображением/видео. Таким образом, нижний край изображения в одном контейнере будет визуально отображаться под верхним краем того, что может быть видео. Мне не нужна помощь в том, как это сделать (я уже это сделал, и это работает) Мне нужна помощь, чтобы страница работала лучше. – paceaux

ответ

1

Из сравнения моего кода как код других людей работал в этой ситуации, я обнаружил, что недостаток был в моем drawFrame() метода, который я использовал, чтобы фактически сделать изображение из видео в холст.

Существовали две основные проблемы:

  1. requestAnimationFrame() работает около 60fps, а также потому, что это видео, не более 30 требуется
  2. я рисовал вырезку в каждом случае 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); 
     } 

    })(); 
} 

Выпуск 1 решается с помощью переменной toggle только нарисовать изображение любой другой время цикла.

Проблема 2 разрешена путем обрезки изображения за пределами цикла.

Эти два изменения сделали заметную разницу в том, как другие элементы на странице загружаются, оживляют и отвечают на пользователя.

Теперь это кажется очевидным, но отсечение каждого кадра в видео значительно дороже, чем отсечение холста .

Большое спасибо пользователю K3N, примеры кода которого помогли мне выяснить проблему.