获取视频第一帧图片设置为竖屏视频的模糊背景

364 阅读4分钟

前言

问题:pc端显示竖屏/横屏手机视频时,宽度不同,布局比较混乱。

解决:统一视频外层div宽度(根据实际情况),设置视频样式 objectFit: 'scale-down'无论容器的大小如何,图片都会保持其原始的宽高比,并且不会放大以填充容器。如果容器比图片小,图片会根据需要进行缩小。

竖屏视频样式问题:由于外层div宽度固定,横屏视频可以占满宽度,竖屏视频自适应宽度会有拉伸变形。

竖屏视频样式问题解决:使用抖音视频展示方式,水平居中并按比例高度自适应外层div,横向留白使用第一帧视频图片并模糊化设置为当前div背景图,与视频统一展示。

css样式设置:

获取视频第一帧做背景图,并将背景图模糊化,设置为视频背景统一视图,从而解决视频无法自适应宽度导致的留白问题。

效果

image.png

css实现

设置父子position定位,在两张图片设置z-index层级,在层级低的图片设置filter模糊效果和opacity透明度。 第一张图片:视频默认显示图片,水平居中,按比例高度占满。 第二张图片:获取视频第一帧,设置模糊和透明度,占满整个div。

<div
  style={{
    height: 180,
    overflow: 'hidden',
    borderTopLeftRadius: '6px',
    borderTopRightRadius: '6px',
    position: 'relative',
  }}
  >
  <img
    id={`myVideo`}
    src={'图片链接'}
    alt="图片错误"
    style={{
      display: 'block',
      width: '100%',
      height: '100%',
      objectFit: 'scale-down',

      position: 'absolute',
      zIndex: 2,
    }}
    />
  <img
    id={`myVideo`}
    src={'截取的视频的第一帧图片'}
    style={{
      display: 'block',
      width: '100%',
      height: '100%',
      objectFit: 'fill',
      background: '#000000',

      position: 'absolute',
      zIndex: 1,
      filter: 'blur(20px)',
      opacity: 0.8,
    }}
    />
</div>

获取视频第一帧

通过getDataURL创建video元素并设置属性,video.currentTime = 1currentTime属性设置为1,这样视频就会从第1秒开始播放,便于获取第一帧。 video.addEventListener('loadeddata', function () {})当视频数据加载完成时,将视频通过canvas绘制成图片,并将图片通过toDataURL转换为base64码。通过base64码在<img/>中显示图片。

/*
* videoId:视频id
* url:视频路径
* callBack:回调函数
*/
export const getVideoFirstFrame = (videoId: string, url: string, callBack: Function) => {
  const getDataURL = (videoId: string, url: string) => {
    return new Promise((resolve, reject) => {
      let dataURL = '';
      let video = document.createElement('video');
      video.setAttribute('id', videoId);
      video?.setAttribute('crossOrigin', 'anonymous');
      video.setAttribute('src', url);
      video?.setAttribute('preload', 'auto');
      // video?.setAttribute('style',' objectFit: fill')
      video.currentTime = 1;
      video.addEventListener('loadeddata', function () {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
        const dataURL = canvas.toDataURL('image/jpeg', 1); // 转换为base64
        resolve(dataURL);

        // 清理资源
        video.removeAttribute('src');
        canvas.width = 0;
        canvas.height = 0;
      });
  
      video.addEventListener('error', (error) => {
        reject(new Error('Failed to load video'));
      });
    });
  };
  getDataURL(videoId, url).then((res) => {
    callBack(res);
  });
};

// 调用
const ruleSize = async (nItemObj: { url: string; id: number }) => {
  let getRes = await getFileData(nItemObj?.url);
  console.log(getRes)
 };

通过url获取图片信息

base64转换成File

// base64转换成File
export const base64ToFile = (dataurl: string, filename?: string) => {
  let newFilename = filename || 'img';
  var arr = dataurl.split(','),
    mime = arr[0]?.match(/:(.*?);/)[1],
    bstr = atob(arr[1]),
    n = bstr.length,
    u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  let result = new File([u8arr], newFilename, { type: mime });
  return result;
};

将小数转换成分数

用于获取图片宽高比例

// 将小数转换成分数
export const decimalToFraction = (decimal: number) => {
  if (decimal < 0) {
    return '-' + decimalToFraction(-decimal);
  }
  var tolerance = 1.0e-6;
  var h1 = 1;
  var h2 = 0;
  var k1 = 0;
  var k2 = 1;
  var b = decimal;
  do {
    var a = Math.floor(b);
    var num = h1 * a + h2;
    var den = k1 * a + k2;
    h2 = h1;
    k2 = k1;
    h1 = num;
    k1 = den;
    b = 1 / (b - a);
  } while (Math.abs(decimal - num / den) > decimal * tolerance);

  return num + '/' + den;
};

将图片url转成base64和图片信息

// 将图片url转成base64
const getDataURL = (url: string) => {
  return new Promise((resolve, reject) => {
    let dataURL = '';
    let img = new Image();
    img.setAttribute('crossOrigin', 'anonymous'); //关键
    img.src = url;
    let imgName = url?.split('/')?.pop();
    img.onload = function () {
      const canvas = document.createElement('canvas');
      let ctx = canvas.getContext('2d');
      canvas.width = img.width;
      canvas.height = img.height;
      ctx?.drawImage(img, 0, 0, canvas.width, canvas.height);
      dataURL = canvas.toDataURL('image/jpeg', 1); //转换为base64
      let fileValue = base64ToFile(dataURL, imgName);
      // 宽高比例  返回1为1:1   返回0.75为3:4
      let aspectRatioValue = img?.width / img?.height;
      const getaspectRatio = (value: number) => {
        let getFraction = decimalToFraction(value);
        let aspectRatio = getFraction?.replace('/', ':');
        return aspectRatio;
      };
      let imgInfo = {
        file: fileValue,
        videoWidth: img?.width, //'宽'
        videoHeight: img?.height, //'高'
        fileValueSize: fileValue?.size, //'大小'
        fileValueName: fileValue?.name, //'名称'
        aspectRatioValue: aspectRatioValue, // 宽高值
        aspectRatio: getaspectRatio(aspectRatioValue), // 宽高比例
      };
      resolve(imgInfo);
    };
  });
};

将url转换成fiel文件并获取数据

// 将url转换成fiel文件并获取数据
export const getFileData = async (url: string, callback?: Function) => {
let result=null
 await getDataURL(url).then((res) => {
    result=res
  });
return result
};

图片信息

image.png

如有问题,请多多指教。🎃