小程序ScrollView跟video组件进入全屏再退出后会自动返回顶部

不知道有没有人遇到过这个bug: video组件进入全屏再退出后会自动返回顶部(目前已知ios)。
我在论坛里面也看到有人提过这个bug,官方态度是未解决。

我整理了一下这个踩坑心得:

  1. 一开始我并没有觉得这2个组件弄在一起会有什么bug(在ScrollView里面用Video组件)。

讲道理这个非常常用,想要长列表,上拉加载和下拉刷新必须用这个组件,想要列表有视频必须用video组件。

我写完后也没发现什么问题,除了有点卡顿问题,然后点开视频播放没问题,实机测试安卓设备也没有问题,但是在苹果设备全屏播放后退出全屏会顶到顶部((灬ꈍ ꈍ灬))什么奇怪的bug。

我翻阅百度和论坛,没看的有什么好解决方法。
video组件进入全屏再退出后会自动返回顶部(目前已知ios)
小程序视频退出全屏产生问题

我尝试了下面评论里面的一个方案:

提供一个曲线救国方案:

1 监听页面的onPageScroll事件,实时全局保存scrollTop

2 监听video的bindfullscreenchange事件(视频进入和退出全屏时触发),当进入全屏,再记录一个值videoScrollTop,将1中的scrollTop设置给它

3 退出全屏时,wx.pageScrollTo({scrollTop: videoScrollTop})

我尝试了一下,并不完美,会一瞬间顶到顶部。

  const resRef = ref.current as any;
  const changeScrollTop = blo => {
    if (blo) {
      wx.setStorageSync("videoScrollTop", resRef.getScrollTop());
    } else {
      resRef.scrollToTop(wx.getStorageSync("videoScrollTop"));
    }
  };

  //封装上拉加载下拉刷新里面
   /**
   * 滚动到顶部
   */
  scrollToTop(top = 0) {
      //跳转到目标top
    this.setState({ scrollTop: top + Math.random() * 0.001 });
  }

  //视频
  <Video
                          onFullscreenChange={event => {
                            wx.setStorageSync("conditionalRefresh", false);
                            changeScrollTop(event.detail.fullScreen);
                          }}
                          src={img.url}
                        />

我放弃了这个方案。

  1. 进入拉锯战。我尝试了很多,比如通过动画方式掩盖一瞬间顶到顶部。上面的方法补丁。但是体验很不友好,我放弃了。

    在某天我灵光一现,我想到了在Video的父级再加一个ScrollView,然后在浏览容器固定高度。用魔法打败魔法。。哈哈哈哈,尝试了一下,发现是可以的,但是我开心没2分钟,发现很卡很卡,根本没办法浏览。

  2. 以上我都准备放弃了,在心里骂了一万遍小程序开发团队!!!我翻阅小程序官方文档,我突然发现了官方自带下拉刷新和上拉加载。

  3. 开干,我弃掉ScrollView,采用官方下拉刷新和上拉加载。
    我用Taro Hooks,你们可以用原生写。

import Taro, {
  usePullDownRefresh,
  useReachBottom,
  usePageScroll,
  useScope
} from "@tarojs/taro";

//私有获取封装的列表useGetList,可以采用自己的方式
 // 获取数据列表
const { noData, state, noMore, list, fetchList, setList } = useGetList(
    [] as any,
    `xxx`,
    { isDebance: true }
  );

  const onRefresh = useCallback(() => fetchList(1), [fetchList]);

  usePullDownRefresh(() => {
    onRefresh();
    Taro.stopPullDownRefresh();
    Taro.hideNavigationBarLoading();
  });

  useReachBottom(() => {
    if (!noMore) {
      fetchList();
    }
  });


  // 视频播放
  const [videoIndex, setVideoIndex] = useState(-1 as any);

  // 播放视频
  const videoPlay = idx => {
    if (videoIndex <= 0) {
      // 没有播放时播放视频
      setVideoIndex(idx);
      const videoContext = Taro.createVideoContext("video" + idx);
      videoContext.play();
    } else {
      // 停止正在播放的视频
      const videoContextPrev = Taro.createVideoContext("video" + videoIndex);
      videoContextPrev.stop();
      // 将点击视频进行播放
      setVideoIndex(idx);
      const videoContextCurrent = Taro.createVideoContext("video" + idx);
      videoContextCurrent.play();
    }
  };

  const stopPlay = idx => {
    const videoContextPre = Taro.createVideoContext("index" + idx);
    videoContextPre.pause();
  };
  const scope = useScope();
  const windowHeight = Taro.getSystemInfoSync().windowHeight; // 可视区域高度
  usePageScroll(() => {
    // eslint-disable-next-line no-undef

    if (videoIndex >= 0 && !isFullscreen) {
      const id = videoIndex;
      selectRect("#item" + id, scope).then(rect => {
        // 我查询的是包裹视频的元素,可根据需求
        const top = rect.top; // 距离顶部高度
        const bottom = rect.bottom;
        const vh = rect.height; // 元素高度
        if (top < 0 || bottom > vh + windowHeight) {
          // 当视频距离顶部为零,测了下,这个为0,视频不可见。
          setVideoIndex(-1);
        }
      });
    }
  });

  const [isFullscreen, setIsFullscreen] = useState(false);
  const onFullscreenChange = blo => {
    setIsFullscreen(blo);
  };



//视频组件
<View className={styles.itemVideoBox}>
                        {idx === videoIndex && (
                          <Video
                            className={styles.video}
                            src={img.url}
                            autoplay
                            onPause={event => {
                              event.stopPropagation();
                              stopPlay(idx);
                            }}
                            onFullscreenChange={event => {
                              event.stopPropagation();
                              onFullscreenChange(event.detail.fullScreen);
                            }}
                            id={`video${idx}`}
                          />
                        )}
                        {idx !== videoIndex && (
                          <View
                            key={`${"id" + index}`}
                            className={styles.posterImgBlock}
                            onClick={event => {
                              event.stopPropagation();
                              videoPlay(idx);
                            }}
                          >
                            <Image
                              src={
                                img.thumb
                                  ? img.thumb
                                  : "默认封面图地址"
                              }
                              mode="aspectFill"
                              lazyLoad
                            />

                            <Image
                              className={styles.palyBtn}
                              src='播放按钮'
                              mode="aspectFill"
                              lazyLoad
                            />
                          </View>
                        )}
                      </View>

less 不要设置高度

page {
  overflow: auto !important;
}
.posterImgNone {
  display: none;
}
.posterImgBlock {
  display: block;
}
.itemVideoBox {
  position: relative;
}
.posterImgBlock,
.posterImgNone {
  margin-bottom: 3px;
  width: 100%;
  height: 200px;
  margin-right: 3px;
  position: absolute;
  top: 0;
  left: 0;
  image,
  img {
    width: 100%;
    height: 100%;
    border-radius: 5px;
  }
  .palyBtn {
    width: 60px;
    height: 60px;
    border-radius: 0;
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -30px;
    margin-left: -30px;
  }
}
/**
 * 查询元素大小
 *
 * @export
 * @param {string} name
 * @param {*} scope
 * @returns
 */
export function selectRect(name: string, scope: any) {
    return new Promise<wx.NodesRef.BoundingClientRectCallbackResult>((resolve) => {
        setTimeout(() => {
            const query = wx.createSelectorQuery().in(scope);
            query.select(name).boundingClientRect((res) => {
                resolve(res as wx.NodesRef.BoundingClientRectCallbackResult);
            }).exec();
        }, 0)
    });
}

我随便加上了列表视频 及 滑过该视频 停止播放。
大功告成!

贴下github代码:https://github.com/ihopefulChina/fix-scrollview-video-bug

至此是遇到了这个大坑过程,以及解决心得。希望能给你们一些帮助。