【问题标题】:How to blur a specific region of a HTML5 video tag?如何模糊 HTML5 视频标签的特定区域?
【发布时间】:2020-02-01 20:36:57
【问题描述】:

对于一个 VueJS 项目,我有一个带有 HTML5 <video> 标签的视频播放器。在这个视频中,我想在左下角显示一些模糊点。

我使用的是画布,纯 CSS 方法,但这些都不起作用。

在 CSS 中:我在视频前面的 div 上使用了过滤器:blur(20px),但它不起作用,模糊会影响 div 的边框而不是中心。

Image with blur test in css

对于画布,我们尝试相同的方法,但我从来没有得到任何单一的模糊效果

我只需要对图像的红色部分进行模糊效果:

The red part need to be blurred

<template>
<div>  
  <div class="contenant">
    <input  type='range' v-model="width" min="0" max="1281" value="0" >
    <img id='image' class="img" src="image1.jpg" alt="test">
    <div id='filtre' v-bind:style="{ width: width + 'px'}"></div>
  </div>
</div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    width: 0,
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.contenant {
  width: fit-content;
}

#filtre {
  height: 96%;
  background-color: red;
  position: absolute;
  bottom: 0;
  opacity: 0.33;
  top: 8px;
  z-index: 2;
  filter: blur(50px);
}

.img{
  position:relative;
}

input[type=range] {
  width: 81%;
  -webkit-appearance: none;
  margin: 10px 0;
  position: absolute;
  z-index: 3;
  height: -webkit-fill-available;
  background-color: transparent;
}

input[type=range]::-webkit-slider-thumb {
  height: 26px;
  width: 26px;
  border-radius: 17px;
  background: #619BFF;
  cursor: pointer;
  -webkit-appearance: none;
}

【问题讨论】:

标签: javascript css canvas html5-video blur


【解决方案1】:

在支持浏览器的情况下,就像使用具有backdrop-filter: blur() CSS 属性的重叠元素一样简单。

// just make the div follow the mouse
const mouse = {
  x: 0,
  y: 0,
  dirty: false
};
const blurme = document.getElementById('soblurme');
document.querySelector('.container')
  .addEventListener('mousemove', (evt) => {
  mouse.x = evt.offsetX;
  mouse.y = evt.offsetY;
  // recently all UI events are already debounced by UAs,
  // but all vendors didn't catch up yet
  if( !mouse.dirty ) {
    requestAnimationFrame( move );
  }
  mouse.dirty = true;
});

function move() {
  blurme.style.left = (mouse.x - 25) + 'px';
  blurme.style.top = (mouse.y - 25) + 'px';
  mouse.dirty = false;
}
.container { position: relative; }
#soblurme {
  position: absolute;
  border: 1px solid white;
  pointer-events: none;
  width: 50px;
  height: 50px;  
  left: 70px;
  top: 20px;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
video {
  width: 100%;
  cursor: none;
}
<div class="container">
  <video autoplay muted controls>
    <source src="https://upload.wikimedia.org/wikipedia/commons/transcoded/2/22/Volcano_Lava_Sample.webm/Volcano_Lava_Sample.webm.360p.webm">
    <source src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4">
  </video>
  <div id="soblurme"></div>
</div>

对于其他人,您需要在画布上再次绘制视频的该部分:

const vid = document.querySelector('video');
const canvas = document.getElementById('soblurme');
const ctx = canvas.getContext('2d');

if( ctx.filter !== "none" ) {
  // in case 2DContext.filter is not supported (Safari), some libraries can do the blur for us
  // I'll let the readers choose the one they prefer and implement it
  console.warn( "we should use a falbback like StackBlur.js" );
}

const spread = 10;
ctx.filter = 'blur(' + spread + 'px)';

const border_width = 1; // because we add a css border around the canvas element

let playing = false;

vid.onplaying = startDrawing;
vid.onpause = stopDrawing;

function startDrawing() {
  playing = true;
  loop();
}
function stopDrawing() {
  playing = false;
}

function loop() {
  if( mouse.dirty ) {
    canvas.style.left = mouse.x + 'px';
    canvas.style.top = mouse.y + 'px';
    mouse.dirty = false;
  }
  draw();
  if( playing ) {
    requestAnimationFrame(loop);
  }
}
function draw() {
  const vid_rect = vid.getBoundingClientRect();
  const can_rect = canvas.getBoundingClientRect();
  const s_x = (can_rect.left - vid_rect.left) + border_width;
  const s_y = (can_rect.top - vid_rect.top) + border_width;
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // if we are lazy, we can draw the whole image
  // but the blur effect is quite heavy to calculate
//  ctx.drawImage(vid, -s_x, -s_y, vid_rect.width, vid_rect.height);

  // so for better performances we may prefer to calculate the smallest area to draw
  // because blur spreads we need to draw outside a little bit anyway
  const offset = spread * 2;
  const output_w = canvas.width + (offset * 2);
  const output_h = canvas.height + (offset * 2);
  const ratio_x = vid_rect.width / vid.videoWidth;
  const ratio_y = vid_rect.height / vid.videoHeight;
  
  ctx.drawImage(
    vid,
    (s_x - offset) / ratio_x, (s_y - offset) / ratio_y, output_w  / ratio_x, output_h / ratio_y,
    -offset, -offset, output_w, output_h 
  );
}

// move with mouse
const mouse = {
  x: 0,
  y: 0,
  dirty: false
};
document.querySelector('.container')
  .addEventListener( 'mousemove', ( evt ) => {
  mouse.x = evt.offsetX - canvas.width / 2;
  mouse.y = evt.offsetY - canvas.height / 2;
  if( !mouse.dirty && !playing ) {
    requestAnimationFrame( loop ); 
  }
  mouse.dirty = true;
});
.container { position: relative; }
#soblurme {
  position: absolute;
  border: 1px solid white;
  pointer-events: none;
  left: 70px;
  top: 20px;
}
video {
  width: 100%;
}
<div class="container">
  <video autoplay muted controls>
    <source src="https://upload.wikimedia.org/wikipedia/commons/transcoded/2/22/Volcano_Lava_Sample.webm/Volcano_Lava_Sample.webm.360p.webm">
    <source src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4">
  </video>
  <canvas id="soblurme" width="50" height="50"></canvas>
</div>

为了有条件地进行,我们可以对它进行特征检测:

const supportsBackdropFilter = (function() {
  const style = document.createElement('_').style;
  style.cssText = 'backdrop-filter: blur(2px);webkit-backdrop-filter: blur(2px)';
  return style.length !== 0 &&
    (document.documentMode === undefined || document.documentMode > 9);
})();

所以大家一起来:

const supports_backdrop_filter = (function() {
  const style = document.createElement('_').style;
  style.cssText = 'backdrop-filter: blur(2px);-webkit-backdrop-filter: blur(2px);';
  return style.length !== 0 &&
    (document.documentMode === undefined || document.documentMode > 9);
})();

const mouse = {
  x: 0,
  y: 0,
  dirty: false
};
const vid = document.querySelector('video');
const canvas = document.getElementById('soblurme');
let playing = false;
const ctx = canvas.getContext('2d');
const spread = 10;
const border_width = 1; // because we add a css border around the canvas element
  
document.querySelector('.container')
  .addEventListener('mousemove', (evt) => {
  mouse.x = evt.offsetX;
  mouse.y = evt.offsetY;
  if( !mouse.dirty ) {
    if( supports_backdrop_filter ) {
      requestAnimationFrame( move );
    }
    else if( !playing ) {
      requestAnimationFrame( loop );
    }
  }
  mouse.dirty = true;
});

function move() {
  canvas.style.left = (mouse.x - 25) + 'px';
  canvas.style.top = (mouse.y - 25) + 'px';
  mouse.dirty = false;
}

// unsupporting browsers 
if( !supports_backdrop_filter ) {
  ctx.filter = 'blur(' + spread + 'px)';

  vid.onplaying = startDrawing;
  vid.onpause = stopDrawing;
}

function startDrawing() {
  playing = true;
  loop();
}
function stopDrawing() {
  playing = false;
}

function loop() {
  if( mouse.dirty ) {
    move();
  }
  draw();
  if( playing ) {
    requestAnimationFrame(loop);
  }
}
function draw() {
  const vid_rect = vid.getBoundingClientRect();
  const can_rect = canvas.getBoundingClientRect();
  const s_x = (can_rect.left - vid_rect.left) + border_width;
  const s_y = (can_rect.top - vid_rect.top) + border_width;
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  const offset = spread * 2;
  const output_w = canvas.width + (offset * 2);
  const output_h = canvas.height + (offset * 2);
  const ratio_x = vid_rect.width / vid.videoWidth;
  const ratio_y = vid_rect.height / vid.videoHeight;

  ctx.drawImage(
    vid,
    (s_x - offset) / ratio_x, (s_y - offset) / ratio_y, output_w  / ratio_x, output_h / ratio_y,
    -offset, -offset, output_w, output_h 
  );
}
.container { position: relative; }
#soblurme {
  position: absolute;
  border: 1px solid white;
  pointer-events: none;
  left: 70px;
  top: 20px;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
video {
  width: 100%;
  cursor: none;
}
<div class="container">
  <video autoplay muted controls>
    <source src="https://upload.wikimedia.org/wikipedia/commons/transcoded/2/22/Volcano_Lava_Sample.webm/Volcano_Lava_Sample.webm.360p.webm">
    <source src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4">
  </video>
  <canvas id="soblurme" width="50" height="50"></canvas>
</div>


OP 在评论中询问,以便模糊实际上扩散而不是像前面的示例中那样清晰。

要在 CSS 中这样做,应该只是添加一个内部元素,我们将在该元素上设置背景过滤器,其中包含一半的模糊和一些边距,然后添加元素上的 blur 与另一半模糊作为简单的 filter 规则。
但是,当前 Blink 中似乎存在一个错误,如果其中一个祖先已经应用了 blur(),则 backdrop-filter 将被简单地丢弃......

所以目前这只适用于 Safari:

// just make the div follow the mouse
const mouse = {
  x: 0,
  y: 0,
  dirty: false
};
const blurme = document.getElementById('soblurme');
document.querySelector('.container')
  .addEventListener('mousemove', (evt) => {
  mouse.x = evt.offsetX;
  mouse.y = evt.offsetY;
  // recently all UI events are already debounced by UAs,
  // but all vendors didn't catch up yet
  if( !mouse.dirty ) {
    requestAnimationFrame( move );
  }
  mouse.dirty = true;
});

function move() {
  blurme.style.left = (mouse.x - 25) + 'px';
  blurme.style.top = (mouse.y - 25) + 'px';
  mouse.dirty = false;
}
.container { position: relative; }
#soblurme {
  position: absolute;
  pointer-events: none;
  width: 50px;
  height: 50px;  
  left: 70px;
  top: 20px;
  --spread: 10px;
  filter: blur(calc(var(--spread) ));
}
#soblurme > div {
  width: calc(100% - var(--spread));
  height: calc(100% - var(--spread));
  backdrop-filter: blur(calc(var(--spread) / 2));
  -webkit-backdrop-filter: blur(calc(var(--spread) / 2));
  padding: 10px;
}
video {
  width: 100%;
  cursor: none;
}
<div class="container">
  <video autoplay muted controls>
    <source src="https://upload.wikimedia.org/wikipedia/commons/transcoded/2/22/Volcano_Lava_Sample.webm/Volcano_Lava_Sample.webm.360p.webm">
    <source src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4">
  </video>
 <div id="soblurme"> <!-- we apply a simple blur() here -->
  <div></div> <!-- this one will get the backdrop-filter -->
 </div>
</div>

不过好消息是画布版本变得更加简单。
我们要做的就是在画布上简单地绘制视频,然后直接从 CSS 对画布元素应用 CSS filter:blur()
由于我们不需要考虑传播,所以 drawImage 的计算更容易,但由于我们不应用任何过滤器,我们甚至可以使用第二个 sn-p 的 cmets 中的惰性版本:

const vid = document.querySelector('video');
const canvas = document.getElementById('soblurme');
const ctx = canvas.getContext('2d');

let playing = false;

vid.onplaying = startDrawing;
vid.onpause = stopDrawing;

function startDrawing() {
  playing = true;
  loop();
}
function stopDrawing() {
  playing = false;
}

function loop() {
  if( mouse.dirty ) {
    canvas.style.left = mouse.x + 'px';
    canvas.style.top = mouse.y + 'px';
    mouse.dirty = false;
  }
  draw();
  if( playing ) {
    requestAnimationFrame(loop);
  }
}
function draw() {
  const vid_rect = vid.getBoundingClientRect();
  const can_rect = canvas.getBoundingClientRect();
  const s_x = (can_rect.left - vid_rect.left);
  const s_y = (can_rect.top - vid_rect.top);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.drawImage(vid, -s_x, -s_y, vid_rect.width, vid_rect.height);
}

// move with mouse
const mouse = {
  x: 0,
  y: 0,
  dirty: false
};
document.querySelector('.container')
  .addEventListener( 'mousemove', ( evt ) => {
  mouse.x = evt.offsetX - canvas.width / 2;
  mouse.y = evt.offsetY - canvas.height / 2;
  if( !mouse.dirty && !playing ) {
    requestAnimationFrame( loop ); 
  }
  mouse.dirty = true;
});
.container { position: relative; }
#soblurme {
  position: absolute;
  pointer-events: none;
  left: 70px;
  top: 20px;
  filter: blur(10px);
}
video {
  width: 100%;
}
<div class="container">
  <video autoplay muted controls>
    <source src="https://upload.wikimedia.org/wikipedia/commons/transcoded/2/22/Volcano_Lava_Sample.webm/Volcano_Lava_Sample.webm.360p.webm">
    <source src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4">
  </video>
  <canvas id="soblurme" width="50" height="50"></canvas>
</div>

【讨论】:

  • 您好@Kaiido,非常感谢您的回答,我会尽快尝试。
  • 您好,只是一个小问题,我怎样才能模糊画布的边框?
  • 究竟是什么边界?那个白色的?它甚至不需要在那里
  • 当我们在图像上应用画布时,我们可以看到画布的极限,我在想如何平滑画布的边缘以使其不那么明显。
  • @YannColin 我也用这个案例更新了答案。
猜你喜欢
  • 2013-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-12
  • 2012-01-29
  • 2012-08-01
  • 2011-07-11
  • 1970-01-01
相关资源
最近更新 更多