页面可见性事件visibilitychange监听

5,363 阅读5分钟

本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

基本应用

监听该页面可视状态

function onVisibilityChange() {
  if (!document[this.hiddenProperty]) {
    console.log('页面显示');
  } else {
    console.log('页面隐藏');
  }
}

function addVisibilityListener() {
  var hiddenProperty = 'hidden' in document ? 'hidden' : 'webkitHidden' in document ? 'webkitHidden' : 'mozHidden' in document ? 'mozHidden' : null;
  var visibilityChangeEvent = hiddenProperty.replace(/hidden/i, 'visibilitychange');
  document.addEventListener(visibilityChangeEvent, onVisibilityChange, false);
}

function removeVisilityListener() {
  var hiddenProperty = 'hidden' in document ? 'hidden' : 'webkitHidden' in document ? 'webkitHidden' : 'mozHidden' in document ? 'mozHidden' : null;
  var visibilityChangeEvent = hiddenProperty.replace(/hidden/i, 'visibilitychange');
  document.removeEventListener(visibilityChangeEvent, onVisibilityChange, false);
}

控制视频暂停⏸播放▶示例

var hidden, visibilityChange;
if (typeof document.hidden !== "undefined") {
  hidden = "hidden";
  visibilityChange = "visibilitychange";
} else if (typeof document.msHidden !== "undefined") {
  hidden = "msHidden";
  visibilityChange = "msvisibilitychange";
} else if (typeof document.webkitHidden !== "undefined") {
  hidden = "webkitHidden";
  visibilityChange = "webkitvisibilitychange";
}

var videoElement = document.getElementById("videoElement");
function handleVisibilityChange() {
  if (document[hidden]) {
    videoElement.pause();
  } else {
    videoElement.play();
  }
}

if (typeof document.addEventListener === "undefined" || hidden === undefined) {
  console.log("This demo requires a browser, such as Google Chrome or Firefox, that supports the Page Visibility API.");
} else {
  document.addEventListener(visibilityChange, handleVisibilityChange, false);
  videoElement.addEventListener("pause", function(){
    document.title = 'Paused';
  }, false);
  videoElement.addEventListener("play", function(){
    document.title = 'Playing';
  }, false);
}

详解

Page Visibility API 标准概述

这是一个页面可见性API,浏览器标签页被隐藏或显示的时候会触发 visibilitychange事件。

这是 HTML5 新提供的一个 api ,作用是记录当前标签页在浏览器中的激活状态。 所谓“激活状态”指当前标签是否正在被用户浏览。

我们知道,平时在 PC 端浏览网页的时候,使用的都是选项卡这种方式浏览网页,使用这种方式浏览,任何给定网页都有可能在后台,因此对用户不可见。页面可见性 API 提供了开发者可以观察的事件,以便了解文档何时可见或隐藏,以及查看页面当前可见性状态的功能。

页面可见性 API 对于节省资源和提到性能特别有用,它使页面在文档不可见时避免执行不必要的任务。

当用户最小化窗口或切换到另一个选项卡时,API会发送 visibilitychange 事件,让开发者知道页面状态已更改。你可以检测事件并执行某些操作或行为。例如,如果你的网络应用正在播放视频,则可以在用户将标签放入背景时暂停视频,并在用户返回标签时恢复播放。 用户不会在视频中丢失位置,视频的音轨不会干扰新前景选项卡中的音频,并且用户在此期间不会错过任何视频。这种体验是用户无感知的,并且对于用户体验是非常友好的。

规范的使用这个API可以减少对用户宽带的占用,减少服务器压力,节省用户内存,以及到达更好的播放效果。

使用场景

  • 网站有图片轮播效果,只有在用户观看轮播的时候,才会自动展示下一张幻灯片。
  • 显示信息仪表盘的应用程序不希望在页面不可见时轮询服务器进行更新。
  • 页面想要检测是否正在渲染,以便可以准确的计算网页浏览量(埋点使用场景)。
  • 当设备进入待机模式时,网站想要关闭设备声音(用户按下电源键关闭屏幕)。

该API的属性和事件

HTML5 中专门为 document 元素添加了相关属性和事件:

属性

    1. Document.hidden 只读属性 布尔值 简单的表示标签页显示或者隐藏 如果页面处于被认为是对用户隐藏状态时返回 true ,否则返回 false
    1. Document.visibilityState 只读属性 是一个用来展示文档可见性状态的字符串,可能的值:
    • visible : 页面内容至少是部分可见。 在实际中,这意味着页面是非最小化窗口的前景选项卡。
    • hidden : 页面内容对用户不可见。 在实际中,这意味着文档可以是一个后台标签,或是最小化窗口的一部分,或是在操作系统锁屏激活的状态下。
    • prerender : 页面内容正在被预渲染且对用户是不可见的(被 document.hidden 当做隐藏). 文档可能初始状态为 prerender,但绝不会从其它值转为该值。 注释:浏览器支持是可选的。
    • unloaded : 页面正在从内存中卸载。 注释:浏览器支持是可选的。
    1. Document.onvisibilitychange
    • EventListener 提供在visibilitychange 事件被触发时要调用的代码。

应用实例场景:带有声音的视频

在此例中,当你切换到另一个标签时视频会暂停,当你返回到当前标签时视频会再次播放,代码如下:

<main>
  <video id="videoElement" controls="" poster="thumbnail.jpg">
    <source src="https://s3-ap-northeast-1.amazonaws.com/daniemon/demos/The%2BVillage-Mobile.mp4" type="video/mp4" media="all and (max-width:680px)">
    <source src="https://s3-ap-northeast-1.amazonaws.com/daniemon/demos/The%2BVillage-Mobile.webm" type="video/webm" media="all and (max-width:680px)">
    <source src="https://s3-ap-northeast-1.amazonaws.com/daniemon/demos/The%2BVillage-SD.mp4" type="video/mp4">
    <source src="https://s3-ap-northeast-1.amazonaws.com/daniemon/demos/The%2BVillage-SD.webm" type="video/webm">
    <p>Sorry, there's a problem playing this video. Please try using a different browser.</p>
  </video>
</main>
  // 设置隐藏属性和改变可见属性的事件的名称
  var hidden, visibilityChange;
  if (typeof document.hidden !== "undefined") {
    hidden = "hidden";
    visibilityChange = "visibilitychange";
  } else if (typeof document.msHidden !== "undefined") {
    hidden = "msHidden";
    visibilityChange = "msvisibilitychange";
  } else if (typeof document.webkitHidden !== "undefined") {
    hidden = "webkitHidden";
    visibilityChange = "webkitvisibilitychange";
  }

  var videoElement = document.getElementById("videoElement");

  // 如果页面是隐藏状态,则暂停视频;如果页面是展示状态,则播放视频
  function handleVisibilityChange() {
    if (document[hidden]) {
      videoElement.pause();
    } else {
      videoElement.play();
    }
  }

  // 如果浏览器不支持addEventListener 或 Page Visibility API 给出警告
  if (typeof document.addEventListener === "undefined" || typeof document[hidden] === "undefined") {
    console.log("This demo requires a browser, such as Google Chrome or Firefox, that supports the Page Visibility API.");
  } else {

  // 处理页面可见属性的改变
  document.addEventListener(visibilityChange, handleVisibilityChange, false);

  // 当视频暂停,设置title
  // This shows the paused
  videoElement.addEventListener("pause", function(){
    document.title = 'Paused';
  }, false);

  // 当视频播放,设置title
  videoElement.addEventListener("play", function(){
    document.title = 'Playing'; 
  }, false);
}

兼容性处理

为了支持老版本的浏览器,我们需要对document.hidden在做一些前缀处理:

function getHiddenProp(){
  var prefixes = ['webkit','moz','ms','o'];
  // 如果hidden 属性是原生支持的,我们就直接返回
  if ('hidden' in document) {
    return 'hidden';
  }
  // 其他的情况就循环现有的浏览器前缀,拼接我们所需要的属性 
  for (var i = 0; i < prefixes.length; i++){
    // 如果当前的拼接的前缀在 document对象中存在 返回即可
    if ((prefixes[i] + 'Hidden') in document) {
      return prefixes[i] + 'Hidden';
    }  
  }
  // 其他的情况 直接返回null
  return null;
}

同样的,我们可以获取 document.visibilityState 属性:

function getVisibilityState() {
  var prefixes = ['webkit', 'moz', 'ms', 'o'];
  if ('visibilityState' in document) {
    return 'visibilityState';
  }
  for (var i = 0; i < prefixes.length; i++) {
    if ((prefixes[i] + 'VisibilityState') in document){
      return prefixes[i] + 'VisibilityState';
    }  
  }
  // 找不到返回 null
  return null;
}

visibilitychange监听事件

你可以在 document 对象上注册一个监听 visibilitychange 事件,根据 document.hidden 或者 document.visibilityState 属性做一些业务逻辑:

var visProp = getHiddenProp();
if (visProp) {
  // 有些浏览器也需要对这个事件加前缀以便识别。
  var evtname = visProp.replace(/[H|h]idden/, '') + 'visibilitychange';
  document.addEventListener(evtname, function () {
    document.title = document[getVisibilityState()]+"状态";
  },false);
}

上面的代码会在页面可见性发生变化时修改 document.title 的值