react 移动端 实现video的自动播放

之前因为要实现视频的自动播放,就先做了一个原生的视频自动播放demo,但是真的想要在实际的项目中实现起来就步步是坑。坑,我已经踩过了,在这里分享一下react在移动端中如何实现视频的自动播放。

项目背景

这个项目是react结合Ios和Android实现的一个APP,应甲方爸爸的要求,实现一个朋友圈内视频的自动播放。大概就是这个样式。

需求分析

当拿到需求的时候第一反应应该就是去百度了吧。网上有很多列子,也有很多踩坑的经验,但是看下来很少有实际完成的。我们逐步来分析一下这个需求:

大方向是实现视频的自动播放,这是我们的主方向,确定!
根据这个我们可以先考虑一下以下几个问题

  1. 应该如何去创建视频元素
  2. 在什么的情况下才去播放这个视频?
  3. 然后在什么情况下,去暂停这个视频?
  4. 又怎么去视频去播放,怎么去暂停?
  5. 我们如何获取到当前是哪个视频?
  6. 又怎么获取到下一个即将来临的视频?
  7. 如何去兼容IOS和Android?
    这是我们在拿到这个需求之后所想到的问题,如果能够顺利的解决这些问题,那我们也就完成了这个需求。下面我们去实现它
实现
1.应该如何去创建视频元素

我们逐个的解决这些问题,先看第一个,《应该如何去创建视频元素》
上文有说到,这是在一个相当于在朋友圈一样的页面下实现的,第一步应该去搭建这个页面,都知道在朋友圈的内容很丰富,有图片,文字,视频,也有可能是两两结合。我建议在做结构搭建的时候用如下的结构:

<div>
	<span>text</span>
	<img></img>
    <video> </video>
 </div>

其主要目的是,有专门的盒子去装载这些内容,这个很重要。因为需要通过获取这个div来获取到div下的video
在这里讲解一下为什么要这么做,也就是实现这个需求的整体思路:在实现的过程中要先判断包含有video标签的div是否被滑到了可视区内,因为所处的环境需要渲染不同的DOM元素,所以每当一个div滑动到可视区内的时候都要用getElementsByTagName("video")来判断这个盒子里是否有video,如果有触发播放事件,如果没有,没有就算了,这是这个需求的整体思路,但是涉及到移动端的如何获取滚动高度,盒子是否滚动到可视区内,获取body的整体高度,video的一下元素属性等。

这里插播一些video的一些属性,和会出现的一些坑

  1. 在ios环境下,视频会被自动全屏播放,安卓环境下还好,为了应对这个问题我们需要一下两个属性:webkit-playsinline='true' playsInline={true},这两个属性规定了video不全屏播放。
  2. 以上两个属性的使用似乎还需要IOS和Android的配合,具体如何配置可自行百度
  3. IOS上触发视频自动播放时,如果视频未加载至可以播放的状态时会出现白屏现象,这个后面会写到我的处理方法。
  4. 浏览器有一个机制,会禁止视频的自动播放,需要用户去触发。把视频做成静音播放即可。属性:muted={true},否则会报错

接下来是关键的一步

2.在什么时候播放视频

应该在什么时候开始播放这个视频呢,答案实际大家都知道,当包含有video的盒子被滚动到可视区内,或者说是屏幕中央时开始播放视频,但是我们如何去判断盒子是否在我们想要的位置呢。接下来要用的阮一峰老师用Javascript获取页面元素的位置这篇文章中的及段代码,这里先列举出来

// 文档的总高度
  getScrollHeight = () => {
    let bSH = '';
    let dSH = '';
    let scrollHeight = 0;
    if (document.body) {
      bSH = document.body.scrollHeight;
    }
    if (document.documentElement) {
      dSH = document.documentElement.scrollHeight;
    }
    scrollHeight = (bSH - dSH > 0) ? bSH : dSH;
    return scrollHeight;
  }

// 浏览器的总高度
  getWindowHeight=() => {
    let windowHeight = 0;
    if (document.compatMode === "CSS1Compat") {
      windowHeight = document.documentElement.clientHeight;
    } else {
      windowHeight = document.body.clientHeight;
    }
    return windowHeight;
  }
  
  // 滚动条在Y轴上滚动的距离
  getScrollTop = () => {
    let scrollTop = 0, bodyScrollTop = 0, documentScrollTop = 0;
    if (document.body) {
      bodyScrollTop = document.body.scrollTop;
    }
    if (document.documentElement) {
      documentScrollTop = document.documentElement.scrollTop;
    }
    scrollTop = (bodyScrollTop - documentScrollTop > 0) ? bodyScrollTop : documentScrollTop;
    return scrollTop;
  }

//获取DOM元素滚动的距离
  getElementViewTop = (element) => {
    let actualTop = element.offsetTop;
    let current = element.offsetParent;
    let elementScrollTop = '';
    while (current !== null) {
      actualTop += current.offsetTop;
      current = current.offsetParent;
    }
    if (document.body.scrollTop === 0) {
      elementScrollTop = document.documentElement.scrollTop;
    } else {
      elementScrollTop = document.body.scrollTop;
    }
    return actualTop - elementScrollTop;
  }

以上四个方法是实现这个需求的关键,

其中getScrollHeightgetWindowHeightgetScrollTop获取文档的总高度,在后面能够用到。getElementViewTop获取DOM元素滚动的距离是这个需求的灵魂所在。

有了这几个关键的方法,我们就可以动手实现自动播放了

  • 判断盒子是否滚动到相应位置,那就必须去监听滚动事件
window.addEventListener('scroll', this.winScroll);

这里有人会说我们是在移动端上实现的是不是应该去监听Touch事件,实际上是没有那个必要的,而且相对于滚动事件,Touch事件没有那么灵敏,而且会有一个惯性的滚动,这时候是不会被监听到的。是可以被禁掉,但是总体体验起来并不是很好。
至于是监听scroll还是Touch关键点是如何有效的获取到滚动的距离和发生滚动的这一动作
看代码:

elementScrollTop = document.documentElement.scrollTop || document.body.scrollTop

每个浏览器获取scrollTop的方法不尽相同,但是document.documentElement.scrollTopdocument.body.scrollTop只有一个能获取到值,也就是说当其中一个获取到值以后,另外一个就会为0,搞定了这里就不需要在去纠结是监听scroll还是Touch,有了监听事件后,就要去完善winScroll这个方法了。

winScroll = (e) => {
    let { videoIndex } = this.state; // 设置一个初始值,来获取div盒子
    let clientData = this.getViewport(); // 获取可视区的大小
    DOMobj = document.getElementById(`content_${videoIndex}`); // 获取div盒子
    let viedos = DOMobj.getElementsByTagName("video"); // 获取盒子下的video标签
    cliHeight = (clientData.height - DOMobj.offsetHeight) / 2; // 这里的cliHeight 是设置一个滑动的范围 
    let arr = this.getElementViewTop(DOMobj); // 原谅这里的不规范  arr 是用来保存获取到盒子距离顶部的距离
    if (viedos.length === 1) { // 判断当前盒子是否含有video
      if (arr <= cliHeight && -arr <= DOMobj.offsetHeight / 2) { // 滑动到此范围内播放,滑出暂停
        viedos[0].controls = false; // 看下方解释
        viedos[0].play();
      } else {
        viedos[0].pause();
      }
    }
    this.changeVideoIndex(DOMobj); // 获取下一个盒子的方法
  };
3. 样式处理
viedos[0].controls = false

了解video的知道controls是控制是否显示浏览器自带的控制栏,这里处理的问题是上文说的IOS视频未加载好视频时出现白屏的情况,如果有controls会触发video自带的加载样式,而且能够有效的规避掉出现白屏的情况。但是还是会有问题
如果网速过慢,视频过大,加载时间长,就会影响隐藏controls
我做的处理是先隐藏controls在去执行播放,这样效果会好很多

另外看下在IOS和Android中导航栏的样式
在这里插入图片描述
这是Android上的,效果很不好,而且白屏的情况只在IOS 的环境下才会出现,所以我加了一个方法

getMODEL =() => {
   let client = '';
   if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) { //判断iPhone|iPad|iPod|iOS
     client = 'iOS';
   } else if (/(Android)/i.test(navigator.userAgent)) { //判断Android
     client = 'Android';
   } else {
     client = 'PC';
   }
   return client;
 }

这里是获取当前设备环境,如果是IOS环境controls设定为true

4.触底

还有一种情况:当滚动条滚动到底部的时候还有一个视频没有被触发该如何处理,
我们需要在往winScroll方法中添加一下的方法

if (this.getScrollTop() + this.getWindowHeight() === this.getScrollHeight()) { // 判断是否触底,触底后索引值➕1
      this.setState({
        videoIndex: videoIndex += 1
      });
      if (viedos.length === 1) { // 判断倒数第二哥盒子是否有视频,有则停止播放
        viedos[0].pause();
      }
      DOMobj = document.getElementById(`content_${videoIndex}`); // 获取最后一个盒子
      viedos = DOMobj.getElementsByTagName("video");
      if (viedos.length === 1) {
        viedos[0].play();
      }
    }
5.什么时候获取下一个盒子

当我们获取到的盒子被滑出了可视区后,我们需要去获取下一个盒子

// 随着滚动改变videoIndex
  changeVideoIndex = (dom) => {
    let { videoIndex } = this.state;
    let arr = this.getElementViewTop(dom);
    // console.log(arr);
    if (-arr > dom.offsetHeight / 2 - 100) { // 这里是实际控制视频到那个位置播放的判断,因为只有videoIndex改变了才会去改变获取到的DOMobj
      this.setState({
        videoIndex: videoIndex += 1
      });
    } else if (-arr < 0) {
      if (videoIndex > 0) {
        this.setState({
          videoIndex: videoIndex -= 1
        });
      }
    }
  }

这就是winScroll中最后调用的那个方法。

到这里我们的需求基本上就实现了,当然环境不一样可能也会出现不同的问题,有问题可以发出来我们共同探讨。

最后我们的video标签应该是这样的

      <video  controls={type === 'iOS' ? true : false} poster={cover} muted={true} webkit-playsinline='true' playsInline={true} x5-playsinline='true' x-webkit-airplay='allow' preload='true'>
      </video>

preload='true'这个属性是video进行预加载,我本以为可以解决白屏的问题,但是不行。
x-webkit-airplay="true"支持Airplay的设备(如:音箱、Apple TV)播放,如果没有需要可以不加

以上种种方法我这里属性和命名都存在不规范的行为,所以请广大程序员的体谅,有些地方在性能方法考虑的还不是很周到,欢迎大佬改良,批评指正。

写到最后有的人可能会说滚动事件触发的那么多,为什么不做防抖和节流的处理。我是这样想的:没必要

总结一下以上方法没有处理好的问题:

  1. 如何有效的规避掉IOS出现白屏的问题
  2. 实际我对视频是否播放和暂停的区间设置的并不是很准确,会出现同时有两个视频一起播放的问题。受第一个评论的大佬启发,可以在加一个方法,当下一个为video即将播放就暂停上一个的播放。如果觉得麻烦就把播放和暂停的区间设置的更加准确一点
分享一下心得

整个做完感觉很简单,但是在实现的过程中就会遇到很多坑,各种兼容问题,适配问题,样式问题等等。没遇到一个坑的时候就意味着我们又可以学到一个新的知识。
第一次拿到这个需求的时候也是抱着试试态度,当时没多想就用原生编写了一个demo,但是真正的拿到项目中的时候就发现问题了而且一度感觉这个东西很不好搞,但是满满的整理好思路逐个击破也就正好了。
这里我想说,当我们拿到一个需求的时候先去定一个最终的目标,然后在化解成一个个的小目标,最后就汇总成了我们自己的思维导图,在从后往前逐个完成就好了

基于react的移动端的视频自动播放功能已完成,有问题可以留言,有新的方式欢迎大佬指导。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
React移动端实现滑动懒加载可以使用react-infinite-scroll-component插件来实现。 安装插件: ``` npm install react-infinite-scroll-component ``` 使用: ``` import React, { Component } from 'react'; import InfiniteScroll from 'react-infinite-scroll-component'; class App extends Component { state = { items: Array.from({ length: 20 }), hasMore: true, }; fetchMoreData = () => { if (this.state.items.length >= 50) { this.setState({ hasMore: false }); return; } // 模拟异步加载数据 setTimeout(() => { this.setState({ items: this.state.items.concat(Array.from({ length: 20 })), }); }, 1500); }; render() { return ( <div> <InfiniteScroll dataLength={this.state.items.length} // 列表长度 next={this.fetchMoreData} // 触底时的回调函数 hasMore={this.state.hasMore} // 是否还有更多数据 loader={<h4>Loading...</h4>} // 加载时的提示 endMessage={<p>No more items</p>} // 列表到底时的提示 > {this.state.items.map((item, index) => ( <div key={index} style={{ padding: 20, border: '1px solid gray' }}> {`Item ${index}`} </div> ))} </InfiniteScroll> </div> ); } } export default App; ``` 在上面的代码中,我们使用了`react-infinite-scroll-component`插件,通过在`InfiniteScroll`组件中设置`dataLength`、`next`、`hasMore`、`loader`和`endMessage`等属性来实现滑动懒加载效果。当用户滑动到列表底部时,`next`函数将被调用,我们可以在该函数中异步加载更多数据,直到`hasMore`为`false`时停止加载。同时,在加载过程中,我们可以显示一个加载提示,当列表到达底部时,我们可以显示一个提示信息告诉用户已经没有更多数据可以加载了。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

琞、小菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值