一天
产品经理:我想做个打开页面点击某个按钮进入全屏模式,再点击退出全屏模式
我:直接 F11 或 ctrl + cmd + f 快捷键就解决了,退出再按一遍
产品经理:这怎么行,用户不懂啥叫快捷键,这个你先看一下,产品经理转身走了
我:mmp... 本来还想说点啥的
一通搜索后...
我:这个吧,可以做,就是吧,部分浏览器版本不太兼容
产品经理:没关系,只考虑主流浏览器主流版本,你评估个时间
我:mmp...,最后加上摸鱼时间 2 天
这个需求重点就是浏览器是否提供全屏模式的方法
正巧,screenfull.js (opens new window) 这个轮子可以完美实现
而且抹平了各浏览器方法名不一致问题
screenfull github 国内镜像仓库 (opens new window)
# screenfull.js 示例
首先看下所提供的功能
示例演示功能如下:
- 当前浏览器是否支持全屏
- 打开全屏
- 退出全屏
- 全屏打开、退出切换
- img 标签全屏显示
- 处于全屏状态的监控
首先
明确全屏模式是指浏览器内容区域全屏
也可以理解为浏览器可视区域
可通过页面标签元素,也只能通过页面标签元素使用全屏接口
比如 document.body 调用全屏 requestFullscreen
接口
div、img
等标签元素当然也可以调用
# 源码解析
下载到本地编辑器中打开体验更佳
# 全屏相关的属性、方法、事件
- requestFullscreen - 打开全屏方法
- exitFullscreen - 退出全屏方法
- fullscreenElement - 获取全屏模式下的元素(不处于全屏时,为 null,基于此判断当前文档是否处于全屏状态)
- fullscreenEnabled - 判断当前浏览器是否支持全屏模式
- fullscreenchange - 监听打开、退出全屏事件
- fullscreenerror - 监听打开、退出全屏出错时事件
更多详细描述,参考 MDN (opens new window)
# 全屏属性、方法、事件归属分类
归属于 document 对象:
- exitFullscreen
- fullscreenElement
- fullscreenEnabled
归属于 html 标签元素:
- requestFullscreen
归属于 document 对象与 html 标签元素:
- fullscreenchange
- fullscreenerror
综上可以看出
全屏的相关属性、方法、事件与 window
对象毫无关系
调用者也不只是单纯的 document 对象
而且只能通过 html 标签元素可调用全屏打开方法
比如 html、div、img
标签元素等
# 源码初探
初始化时
匿名函数自执行包裹了整个源码
当然整个源码处于严格模式下
初始声明 commonjs
支持
判断了 document
对象
var isCommonjs = typeof module !== 'undefined' && module.exports;
// document 如果不是 window 对象属性就是 {}
var document = typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document : {};
2
3
源码最后,根据 commonjs
支持情况,导出不同格式的模块
// 不同运行环境导出不同的模块
if (isCommonjs) {
// 应用于 webpack 等打包工具或 Node 中 commonjs 模块
module.exports = screenfull;
} else {
// 应用于浏览器中的全局模块
window.screenfull = screenfull;
}
2
3
4
5
6
7
8
# 源码解析
抹平各浏览器全屏接口差异
判断当前浏览器是否支持全屏模式
// fn 函数自执行,用于抹平 Chrome、Firefox、IE 浏览器差异
// fn 为 false 时表示当前浏览器不支持全屏模式
var fn = (function () {
var val;
// 定义兼容 Chrome、Firefox、IE 方法数组
var fnMap = [
[
'requestFullscreen',
'exitFullscreen',
'fullscreenElement',
'fullscreenEnabled',
'fullscreenchange',
'fullscreenerror'
],
// New WebKit
[
'webkitRequestFullscreen',
'webkitExitFullscreen',
'webkitFullscreenElement',
'webkitFullscreenEnabled',
'webkitfullscreenchange',
'webkitfullscreenerror'
],
// Old WebKit
[
'webkitRequestFullScreen',
'webkitCancelFullScreen',
'webkitCurrentFullScreenElement',
'webkitCancelFullScreen',
'webkitfullscreenchange',
'webkitfullscreenerror'
],
[
'mozRequestFullScreen',
'mozCancelFullScreen',
'mozFullScreenElement',
'mozFullScreenEnabled',
'mozfullscreenchange',
'mozfullscreenerror'
],
[
'msRequestFullscreen',
'msExitFullscreen',
'msFullscreenElement',
'msFullscreenEnabled',
'MSFullscreenChange',
'MSFullscreenError'
]
];
var i = 0;
var l = fnMap.length;
var ret = {};
for (; i < l; i++) {
val = fnMap[i];
// exitFulllscreen 归属于 document 对象专有
// 判断 exitFulllscreen 或带前缀的是否在 document 对象上
// 若不在,表示当前浏览器不支持全屏模式
if (val && val[1] in document) {
for (i = 0; i < val.length; i++) {
// 统一 ret key 值名称,值根据浏览器支持情况自动处理
// 优秀,保持对象接口名称的一致性,不必添加前缀不前缀的
// 最终对象输出统一为 fnMap[0] 中的接口
ret[fnMap[0][i]] = val[i];
}
return ret;
}
}
return false;
})();
// fn 为 false 当前浏览器表示当前浏览器全屏模式不可用,直接返回
if (!fn) {
if (isCommonjs) {
module.exports = {
isEnabled: false
};
} else {
window.screenfull = {
isEnabled: false
};
}
return;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
若当前浏览器支持全屏模式:
定义对外统一接口对象 screenfull,所以在浏览器中可通过
screenfull.request
支持打开全屏封装、简化全屏相关接口、属性、事件
// 将全屏事件归属到一个对象中、方便使用
var eventNameMap = {
change: fn.fullscreenchange,
error: fn.fullscreenerror
};
var screenfull = {
// 打开全屏
// 注意只有 html 标签元素才支持此方法
request: function (element, options) {
return new Promise(function (resolve, reject) {
var onFullScreenEntered = function () {
this.off('change', onFullScreenEntered);
resolve();
}.bind(this);
this.on('change', onFullScreenEntered);
// 使用 html 标签元素调用打开全屏,若未传入时,默认 html 标签元素
element = element || document.documentElement;
// options 为打开全屏时的配置信息
// 目前只有 navigationUI 这个配置项
// 详情: https://developer.mozilla.org/zh-CN/docs/Web/API/FullscreenOptions
// 表示是否显示用户界面其他元素
var returnPromise = element[fn.requestFullscreen](options);
if (returnPromise instanceof Promise) {
returnPromise.then(onFullScreenEntered).catch(reject);
}
}.bind(this));
},
// 退出全屏
exit: function () {
return new Promise(function (resolve, reject) {
if (!this.isFullscreen) {
resolve();
return;
}
var onFullScreenExit = function () {
this.off('change', onFullScreenExit);
resolve();
}.bind(this);
this.on('change', onFullScreenExit);
// 通过 document 对象调用
var returnPromise = document[fn.exitFullscreen]();
if (returnPromise instanceof Promise) {
returnPromise.then(onFullScreenExit).catch(reject);
}
}.bind(this));
},
// 打开、退出全屏切换
toggle: function (element, options) {
return this.isFullscreen ? this.exit() : this.request(element, options);
},
// 监听打开、退出全屏切换快捷方式
onchange: function (callback) {
this.on('change', callback);
},
// 监听打开、退出全屏切换错误快捷方式
onerror: function (callback) {
this.on('error', callback);
},
on: function (event, callback) {
var eventName = eventNameMap[event];
if (eventName) {
// 在 html 标签元素上监听打开、退出全屏事件时
// 默认冒泡到 document 对象上
// 所以最终只在 document 对象上监听即可
// 如果就想监听当前标签元素的
/**
当前元素上通过 stopPropagation 阻止事件冒泡,这样 document 上就监听不到了
ele.addEventListener('fullscreenchange', function(ev) {ev.stopPropagation(), false)
*/
document.addEventListener(eventName, callback, false);
}
},
off: function (event, callback) {
var eventName = eventNameMap[event];
if (eventName) {
document.removeEventListener(eventName, callback, false);
}
},
raw: fn
};
// 全屏属性的封装,直接挂载到 screenfull 对象
Object.defineProperties(screenfull, {
// 当前文档是否处于全屏模式
isFullscreen: {
get: function () {
return Boolean(document[fn.fullscreenElement]);
}
},
// 处于全屏模式文档元素
element: {
enumerable: true,
get: function () {
return document[fn.fullscreenElement];
}
},
// 浏览器是否兼容全屏模式
isEnabled: {
enumerable: true,
get: function () {
// Coerce to boolean in case of old WebKit
return Boolean(document[fn.fullscreenEnabled]);
}
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
以上就是 screenfull.js
源码全部内容
# 扩展
iOS Safari 没有全屏 API,但 Chrome(Android 版)、Firefox 和 IE 11+ 上则有相应的 API,另外 API 存在命名不统一的问题,所以最好使用三方库解决
谷歌开发者也建议使用 screenfull.js
解决
另外,Web 全屏模式的打开
除了使用浏览器全屏模式 API 打开外
还有以下两种方式:
# 从主屏幕以全屏模式启动页面
iOS: 自从 iPhone 发布以来,用户就一直能将网络应用安装到主屏幕,并以全屏模式启动。
<meta name="apple-mobile-web-app-capable" content="yes">
Chrome(Android 版):
<meta name="mobile-web-app-capable" content="yes">
网络应用清单(Chrome、Opera、Firefox、Samsung):
- 将清单的相关信息告知浏览器
- 说明启动方法
<link rel="manifest" href="/manifest.json">
{
"short_name": "Kinlan's Amaze App",
"name": "Kinlan's Amazing Application ++",
"icons": [
{
"src": "launcher-icon-4x.png",
"sizes": "192x192",
"type": "image/png"
}
],
"start_url": "/index.html",
// 这一句才是关键,全屏模式
"display": "fullscreen",
"orientation": "landscape"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 脚本 hack 自动隐藏地址栏
自动隐藏地址栏来“伪造全屏模式” 需要多加测试,处理兼容问题
window.scrollTo(0,1);
以上内容可参考谷歌开发文档 (opens new window)
# screenfull.js Typescript 声明文件问题
使用 Typescript 开发时
// 引入 screenfull
import screenfull from 'screenfull'
// 查看是否处于全屏状态
screenfull.isFullscreen
2
3
4
5
报错如下:
Property 'isFullscreen' does not exist on type 'Screenfull | { isEnabled: false; }'. Property 'isFullscreen' does not exist on type '{ isEnabled: false; }'.
意思就是 isFullscreen
不在 screenfull
这个对象上
找到 screenfull.d.ts
声明文件
找到如下声明处:
declare let screenfull: screenfull.Screenfull | {isEnabled: false};
将 {isEnabled: false}
删除
declare let screenfull: screenfull.Screenfull;
这样,不报错了
# 小结
通过对 screenfull.js
实际使用演示了所解决需求痛点
对其源码的解读明白了内部原理及实现细节
比如,抹平各浏览器差异
比如,标签元素、document 对象对于事件监听的合并处理
比如,全屏 API 部分归属于 html 标签元素,部分归属于 document 对象
通过扩展看到了全屏模式不同的解锁姿式,不仅限于 API
还可以通过
meta 配置
manifest.json 清单
甚至通过脚本的伪造全屏模式
最后解决了使用时 Typescript 声明文件的问题
扫一扫,微信中打开