我的第一思路就是监听滚动位置来判断某个视频元素是否到达指定区域内, 但是这种方式需要处理的条件很多, 比如边界条件判断, 滚动方向判断等, 而且频繁触发还会出现性能问题.
前言
去年利用空余时间研究了一下 JavaScript 的 Intersection Observer API, 发现其有很大的应用场景, 比如图片或者内容的懒加载, 视差动画等. 我也在之前的文章中详细介绍了 3 种 Observer(观察者) 的用法, 包括位置监听, dom 变化监听以及窗口变化监听, 它们有非常多的应用场景, 所以很有必要研究明白, 感兴趣的可以读完本片文章之后学习一下 (几个非常有意思的 JavaScript 知识点总结).
这里有一个很常见的例子, 平时喜欢看短视频的朋友可能会注意到, 我们在浏览某视频头条时, 滚动视频列表, 当某一个视频滚动到手机的一定位置时 (一般可以看成是屏幕中心), 该视频会自动播放, 当移出指定区域后视频会自动关闭并播放移入指定区域的下一个视频, 如下:
作为一名好奇心极强的前端工程师, 有必要好好研究一下其内部实现.
我的第一思路就是监听滚动位置来判断某个视频元素是否到达指定区域内, 但是这种方式需要处理的条件很多, 比如边界条件判断, 滚动方向判断等, 而且频繁触发还会出现性能问题.
好在之前深入研究过 Intersection Observer API, 发现可以使用它提供的 API, 很方便的监听到元素在指定根元素下的位置变化, 并做一些自定义操作:
接下来我将利用 Intersection Observer 提供的 API 来实现视频在滚动的过程中自动播放的功能, 如果对该 API 不太熟悉的朋友可以移步
几个非常有意思的 JavaScript 知识点总结
视频播放插件笔者将使用比较流行的 Dplayer, 它可以很方便的操作视频的展现并实现很好的排他性播放控制, 并且支持弹幕.
正文
根据以上的介绍我们大致了解了具体的需求, 接下来我们就来基于 Intersection Observer API 实现一下它. 思路大致如下图所示:
具体思路就是我们可以把 Intersection Observer 的根元素的 rootMargin(即根元素的外边距) 设置为如上图蓝色所示区域, 然后当视频完全进入该区域内后 (也就是 thresholds 阈值为 1 时), 触发当前视频的播放即可. 因为我们使用的是 Dplayer, 所以我们只要将其配置属性中的 mutex 属性设置为 true(为 true 时会阻止多个播放器同时播放, 当前播放器播放时暂停其他播放器). 有关设置 rootMargin 的知识, 可以参考下图介绍:
rootMargin 接收格式如下:"10px 0px 10px 0px", 从左到右数字依次代表 top(上) right(右) bottom(下) left(左) 边距, 当然我们单位也可以使用百分比 (%), 为正值时代表扩大更元素的边距范围, 负值代表缩小根元素的边距范围, 这里我们应该缩小范围, 所以 rootMargin 我们可以这么设置 "-180px 0px -180px 0px", 这样上下的边距就会缩小, 当然大家也可以根据需求设置不同的值.
有了以上思路之后我们就可以实现上文动图所展示的效果了. 笔者将采用 react 来实现, 在实现之前我们先准备几个视频素材, 然后实现列表基本框架:
- import React, { useEffect, useState } from 'react'
- import VideoItem from 'components/VideoItem'
- import styles from './videoList.less'
- const data = [
- // 视频列表
- ]
- function VideoList(props) {
- useEffect(() => {
- let observerVideo = new IntersectionObserver(
- (entries, observer) => {
- entries.forEach(entry => {
- // 当移入指定区域内后, 播放视频
- if(entry.intersectionRatio === 1) {
- // 一些操作
- return
- }
- // 停止监听
- // observer.unobserve(entry.target);
- });
- },
- {
- root: document.getElementById('scrollView'),
- rootMargin: '-180px 0px -180px 0px',
- threshold: 1
- }
- );
- document.querySelectorAll('.video-item').forEach(video => { observerVideo.observe(video) });
- }, [])
- return
- {
- data.map(item => {
- return
- })
- }
- }
- export default VideoList
以上代码中 VideoItem 组件我们后面会介绍, 现在有个问题是我们已经监听到了需要自动播放的视频元素, 但是我们如何通知 VideoItem 组件让其播放呢? 这里笔者实现思路是给 VideoItem 添加一个自定义属性, 该属性的值就是当前 video 的 src, 我们在监听到某个视频元素需要播放时, 我们可以获取到之前设置的自定义属性, 然后作为 prop 传给 VideoItem, 当 VideoItem 组件监听到该 prop 变化时, 并且等于自身的 src, 此时则触发视频播放. 代码如下:
- // VideoItem.JS
- import React, { useRef, useEffect } from 'react';
- import DPlayer from 'dplayer';
- export default (props) => {
- let videoRef = useRef(null)
- let dpRef = useRef(null)
- let { src, groupName, curPlaySrc } = props
- useEffect(() => {
- dpRef.current = new DPlayer({
- container: videoRef.current,
- screenshot: true,
- video: {
- url: src,
- thumbnails: 'logo.png'
- },
- logo: 'logo.png'
- });
- }, [])
- useEffect(() => {
- // 当当当前应该播放的视频 url 等于当前视频组件的 src 时, 播放视频
- if(curPlaySrc === src) {
- dpRef.current.play()
- }
- }, [curPlaySrc])
- return
- }
此时视频列表页代码如下:
- // ...
- function VideoList(props) {
- const [curPlaySrc, setCurPlaySrc] = useState('')
- useEffect(() => {
- let observerVideo = new IntersectionObserver(
- (entries, observer) => {
- entries.forEach(entry => {
- // 当移入指定区域内后, 播放视频
- if(entry.intersectionRatio === 1) {
- // 设置当前因该播放的视频 url
- setCurPlaySrc(entry.target.dataset.src)
- return
- }
- });
- },
- {
- root: document.getElementById('scrollView'),
- rootMargin: '-180px 0px -180px 0px',
- threshold: 1
- }
- );
- document.querySelectorAll('.video-item').forEach(video => { observerVideo.observe(video) });
- }, [])
- return
- {
- data.map(item => {
- return
- })
- }
- }
以上步骤即完成了基于指定区域自动播放视频的功能, 效果如下:
体验地址
视频自动播放 demo
仿微信朋友圈动态 demo
最后
如果想学习更多 H5 游戏, webpack,node,gulp,CSS3,JavaScript,Node.JS,canvas 数据可视化等前端知识和实战, 欢迎在公号《趣谈前端》加入我们的技术群一起学习讨论, 共同探索前端的边界.
来源: http://developer.51cto.com/art/202108/679783.htm