12 个鲜为人知的 JavaScript Web API,助你网站性能飞速提升
Web API 超过 97 个,而您只使用了其中的 5%。让我们解锁剩下的 95%!
在对规范的黑暗领域进行了长时间的探索之后,我意识到有很多技术被遗漏了。
我写这篇文章的目的是让大家了解这些 API。我会用实际的例子向你证明,有些 API 确实值得一试!
每个部分都会彻底检查每个 API,并提供一个有趣的示例,完美地展示实际用例。
广告:喜欢免费赠品吗?🤑
我制作了一套包含 100 个免费悬停动画的包。立即获取,分享,随心所欲地使用。它永远属于你!❤️
📑 目录
- 🤳 屏幕方向 API
- 📺️ 全屏 API
- 📲 交叉口观察器 API
- 💤 屏幕唤醒锁定 API
- 💻️ 屏幕捕获 API
- 📔 IndexedDB API
- 🧠 本地和会话存储 API
- 🎨Houdini API
- 🕸️ Web 共享 API
- 📋️ 剪贴板 API
- ✒️ 选择 API
- 👀 页面可见性 API
关于肖像模式的警告
屏幕太窄。请尝试横向模式。
某些应用程序(例如非线性视频编辑器)不适用于垂直设备:它们在窄屏幕上无法正常工作!
当然,网络应该是响应式的,但将整个宽布局移植到窄显示器上并不总是值得的。
如果我们能在设备旋转方向错误时提醒用户,那岂不是很棒?让我来介绍一下……屏幕方向 API!
为了避免错误,务必检查屏幕方向 API 的支持情况。很简单,只需:if ('orientation' in screen)
。您将在本文中反复看到这种模式。
屏幕方向 API
浏览器公开了一个名为 的全局变量screen
,我们将使用它来访问所需的信息。该[ScreenOrientation](https://developer.mozilla.org/en-US/docs/Web/API/ScreenOrientation)
实例可以通过 访问screen.orientation
。在本节中,我们将一直使用这个对象。
检测屏幕方向
屏幕方向类型和角度:https://w3c.github.io/screen-orientation/#dfn-screen-orientation-values-table
与普遍的看法相反,屏幕的定向方式有四种,如上图所示。但我们只想知道屏幕是处于纵向模式还是横向模式,编写一个函数来告诉我们这一点很容易:
function getOrientation() {
const isPortrait = screen.orientation.type.startswith('portrait')
return isPortrait ? 'portrait' : 'landscape'
}
锁定屏幕方向
像 Instagram 这样的原生应用在使用时会锁定屏幕方向。随着PWA 与原生应用之间的界限日益模糊,此功能也出现在网页端也就不足为奇了。
虽然支持较少,但也可以使用以下代码片段锁定和解锁屏幕方向:
screen.orientation.lock(orientation)
不要忘记处理错误,因为正如我已经说过的,此功能没有得到很好的支持。
让您的网站获得全屏体验
浏览器在我们的网站上覆盖了大量的 UI 元素,分散了用户的注意力,使其无法关注重要内容。
Chrome 移动版的屏幕截图,突出显示了浏览器的 UI 元素。
当涉及沉浸式内容时,这个问题尤其严重,例如:
- 电影,
- 游戏,
- 最大化图像。
这样的清单还可以很长。
值得庆幸的是,全屏 API 的出现拯救了我们!
所有现代浏览器都很好地支持此功能,因此不必担心使用它。
进入全屏模式
令人惊讶的是,任何 DOM 元素都可以进入全屏模式:
el.requestFullscreen()
然而,大多数时候,我们希望整个页面进入全屏。根文档元素——<html>
可以在JavaScript中使用 访问document.documentElement
。
因此,在网络上看到这样的代码片段并不罕见:
document.documentElement.requestFullscreen()
退出全屏模式
退出方式多种多样。其中一些是浏览器默认的键盘快捷键:ESC
和F11
。
还可以通过切换标签Ctrl+Tab
或跳转窗口来离开Alt+Tab
。
然而,最重要的退出机制是由你——开发者——提供的。你可以使用以下代码以编程方式退出全屏模式:
document.exitFullscreen()
然而,在部署环境中,在调用此函数之前检查它是否存在以避免错误非常重要:
if (document.exitFullscreen) {
document.exitFullscreen()
}
验证用户是否处于全屏模式
如果我们想要实现如Codepen 开头所见的全屏切换,我们需要一种方法来确定全屏模式是否处于活动状态。
使用以下代码片段完全可以实现这一点:
document.fullscreenElement // returns 'null' or the fullscreen-enabled DOM element
为了更好的浏览器兼容性,我们必须检查多个属性:
document.fullscreenElement
|| document.mozFullscreenElement
|| document.msFullscreenElement
|| document.webkitFullscreenElement
有了这个,我们就可以实现全屏切换:
function toggleFullScreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
}
当元素进入视口时为其添加动画
考虑一下当元素进入视图时你需要做某事的所有时间:
一个比较简单的解决方案是getBoundingClientRect
每次滚动时都调用它。我的意思是……它确实有效!
然而,它的效率非常低。它在主线程上运行,因此我们注册的事件监听器越多,我们的应用程序就会变得越慢。
值得庆幸的是,浏览器工程师为我们带来了Intersection Observer API:这是一种高效的解决方案,将所有优化委托给浏览器,以便我们(Web 开发人员)可以专注于重要的事情。
我们将制作一个非常酷炫的效果,让文本元素仅在进入视野时高亮显示,打造出一个时尚现代的动画效果,相信读者一定会喜欢。您可以在上方的 Codepen中亲眼见证。
创建观察者
在我们开始监听交叉事件之前,我们必须创建一个处理所有后台任务的观察者对象:
const options = {
root: null, // use viewport
threshold: 0.75
}
const observer = new IntersectionObserver(callback, options)
你可能已经注意到了threshold
。这是一个选项,它告诉浏览器仅当元素的N%可见时才触发交叉事件。
处理交叉路口事件
让我们定义回调,即在发生交叉事件时调用的函数。
我们希望仅当元素在视口中显示至少 N% 时才处理事件:
function callback(entry) {
if (entry.intersectionRatio > 0.75) {
// ...
}
}
现在是时候决定元素进入视图后该如何处理了。在本例中,我们只需为其分配一个.active
类名,并将动画的职责委托给 CSS。
function callback(entry) {
if (entry.intersectionRatio > 0.75) {
entry.target.classList.add('active')
}
}
一旦它离开屏幕,我们也可以“撤消”此效果:
function callback(entry) {
if (entry.intersectionRatio > 0.75) {
entry.target.classList.add('active')
} else {
entry.target.classList.remove('active')
}
}
要深入了解 IntersectionObserver API,请阅读Denys Mishunov 的这篇精彩文章。
防止屏幕变暗
暂停 Youtube 上的太空发射视频,显示视频播放器的控制。
长视频需要屏幕保持常亮,即使没有任何交互。这种行为通常出现在原生应用中,但借助屏幕唤醒锁定 API,网页端也会出现这种情况!
此 API 还有许多其他用例:
- 在线游戏,
- 演示文稿,
- 在画布上绘画,
- 相机,
- 流媒体,
- 计时器。
并且这个名单永无止境。
让我们更深入地探究其内部工作原理!
Firefox 和 Safari 尚未支持此功能。因此,最好先检查其是否可用,以避免出现各种错误:if ('wakelock' in navigator)
获取唤醒锁
视频播放器(例如 Youtube)可能会在其play
功能中获取唤醒锁:
let wakelock = null
async function play() {
// …
wakelock = await navigator.wakeLock.request('screen')
}
如果用户的电池电量太低,则预计会出现故障。
释放唤醒锁
永远保持唤醒锁状态是不好的做法,因为这会损害用户的电池,甚至可能降低性能。因此,请务必尽可能地释放唤醒锁:
async function pause() {
// ...
await wakelock.release()
}
每当用户离开您的网站标签时,唤醒锁就会自动释放。
在这种情况下,您应该通过监听事件来重新获取它,我们将在另一部分中visibilitychange
详细了解。
但简而言之,它是在用户离开/进入网站标签时触发的。
document.addEventListener('visibilitychange', async () => {
if (wadocument.addEventListener('visibilitychange', async () => {
if (wakelock !== null && document.visibilityState === 'visible') {
wakelock = await navigator.wakeLock.request('screen')
}
})
录制屏幕
现在,网页端的屏幕录制应用越来越多。但它们究竟是如何做到的呢?答案出奇地简单。
他们成功的秘诀是屏幕捕获 API,这是一个易于使用的界面,允许用户以多种方式记录他们的屏幕:
- 整个屏幕,
- 特定窗口,
- 特定标签。
它还具有额外的精美功能,包括但不限于:
- 模糊/覆盖重叠的窗口以避免意外共享敏感信息,
- 隐藏/显示光标,
- 录制声音。
浏览器兼容性
我不想成为坏消息的传播者,但目前还没有移动浏览器支持此 API 。
另一方面,它得到了现代桌面浏览器的良好支持!(当然,Internet Explorer 除外)
开始屏幕捕获
有了这个 API,录制屏幕就变得非常简单:
const options = {
video: {
cursor: 'always' // show the cursor
},
audio: false // don't record audio
}
navigator.mediaDevices.getDisplayMedia(options) // returns a promise
如果我告诉你就是那样,你会相信吗?我从不说谎。
屏幕捕获提示显示 3 种类型:整个屏幕、窗口、选项卡。
上述函数告诉浏览器显示选择所需记录表面的提示,如上图所示。(请在本节开头的 codepen中亲自尝试)
预览录音
如果我们能够准确地看到网站所看到的内容就太好了。值得庆幸的是,这很容易做到:
<video autoplay id="preview"></video>
HTML 部分就是这样。现在,我们来看看 JavaScript 逻辑:
previewElem = document.getElementById('preview')
previewElem.srcObject = await navigator.mediaDevices.getDisplayMedia(options)
就这样!现在您可以实时查看正在录制的内容了。
停止屏幕捕获
只需一个方法,我们就能实现所有功能!请注意,首先要有一个预览元素,正如上一节所示。
let tracks = previewElem.srcObject.getTracks()
tracks.forEach(() => track.stop())
previewElem.srcObject = null
将表格数据存储在设备上的数据库中
您的浏览器中隐藏着一整个 NoSQL 数据库系统,可以通过IndexedDB API 进行访问!
每个操作都是异步的,因此不会减慢其他操作的速度。一旦用户清除浏览器缓存或本地存储的数据,缓存也会被清除。
除此之外,它还支持常见的搜索、获取和放置操作,并附带事务处理功能。它可以存储几乎所有类型的数据,包括但不限于File
……、图像和视频Blob
,String
当然还有……。
遗憾的是,并非所有浏览器都一致同意应该支持哪种类型的数据。例如,iOS 上的 Safari 浏览器就无法存储 Blob 数据。不过,所有其他格式都
ArrayBuffer
可以转换为,并且所有平台都对 格式提供了良好的支持。
存储限制不是问题,因为大多数浏览器都会分配大量空间,您的网站可以自由使用。此外,每个数据库不仅与域名绑定,还与特定的子域名绑定。此外,浏览器兼容性完全不是问题,即使在 IE11 上也是如此。
我们可以利用这个 API 做很多事情,包括但不限于:
- 存储结构化数据以供离线使用,
- 加快重复访问的加载时间,
- 缓存用户生成的数据,
- 在将数据上传到服务器之前临时保存数据。
让我们看看如何在 IndexedDB 中存储联系人数据!
使用 IndexedDB
在我们做任何事情之前,我们应该在 IndexedDB 上使用一个包装库,因为默认情况下它太复杂了;它使用事件而不是承诺。
import { openDB } from 'idb';
const db = await openDB('contacts', 1, {
// Creates a new database if it doesn't exist.
upgrade(db) {
db.createObjectStore('contacts', {
// The 'id' property of the object will be the key.
keyPath: 'id',
// If it isn't explicitly set, create a value by auto incrementing.
autoIncrement: true,
})
// Create an index on the 'name' property of the objects.
store.createIndex('name', 'name');
}
})
完成这些之后,我们就可以开始存储结构化数据了,就像没人管一样!
await db.add('contacts', {
name: 'John Doe',
age: 25,
email: 'johndoe@john.doe'
})
我们可以轻松地检索所有内容:
// Get all contacts in name order:
const contacts = await db.getAllFromIndex('contacts', 'name')
对于这个简单的用例,我们需要了解的就这些。如果您仍然感兴趣,可以深入了解文档。
即使用户离开,文本数据仍存储在设备上
虽然我们可以使用 IndexedDB 在浏览器上存储大量复杂的数据,但仍然需要考虑其他时候我们只需要保存一个简单的键值对:
- 登录信息,
- 时事通讯订阅状态,
- 同意使用 Cookie,
- 分析跟踪像素。
值得注意的是,敏感信息(密码、信用卡信息、社会保险号等)绝对不能保存在浏览器中,因为除了其他漏洞之外,它还容易受到 XSS 攻击。
针对这种简单的情况,有一个专门的工具,叫做Web Storage API。和 IndexedDB 一样,它与特定的子域名绑定。如果用户清空浏览器的缓存或数据,它也会被清除。
在此 API 中,您会发现两种类型的存储:localStorage
和sessionStorage
。它们提供不同的优势:
- 即使用户离开网站,本地存储仍会保留数据,而会话存储在关闭选项卡后会清除所有数据。
- 本地存储可以存储更多数据,而会话存储的最大容量为 5MB。
使用本地存储
假设我们正在实现一个新闻通讯订阅表单。我们不想在用户订阅后继续显示它,所以我们将使用localStorage
全局变量来有条件地显示它:
function subscribe() {
// ...
localStorage.setItem('is-newsletter-subscribed', true)
}
您现在可以使用 DevTools 查看保存在计算机中的新项目。
现在,让我们编写一个函数来决定是否显示订阅表单:
function isSubscribed() {
return localStorage.hasOwnProperty('is-newsletter-subscribed') ? localStorage.getItem('is-newsletter-subscribed') : false
}
如你所见,我们首先检查该newsletter-subscribed
项目是否存在。如果存在,我们就使用 返回它的值;否则,由于用户尚未订阅,因此getItem()
直接返回。false
创建位置感知的涟漪效应
随着网络的发展,特效也随之发展。如今,CSS 属性已不足以实现我们最疯狂的梦想。
我们的最后手段曾经是 GIF 和图像,但有了CSS Painting API,情况就不再如此了!
现在,我们可以使用 HTML Canvas 附带的所有时尚效果在元素的背景上绘制任何内容。
浏览器兼容性不太好。iOS上的 Firefox 和 Safari 尚未支持它。因此,运行以下命令非常重要:if ('paintWorklet' in CSS)
让我们构建一个没有任何伪元素的涟漪效果,受到Google 自己的实现的启发。
JavaScript 逻辑
为了使此效果起作用,我们需要使用 JavaScript 事件来获取光标x
和y
位置:
// index.js
button.addEventListener('click', (e) => {
const {x: offsetX, y: offsetY} = button.getBoundingClientRect()
const [x, y] = [e.clientX - offsetX, e.clientY - offsetY]
button.style.setProperty('--x', x)
button.style.setProperty('--y', y)
// ...
})
由于涟漪效果是一种随时间演变的动画,我们需要使用变量来跟踪其时间线tick
:
// index.js
button.addEventListener('click', (e) => {
// ...
const start = performance.now()
requestAnimationFrame(function step(now) {
const tick = Math.floor(now - start)
button.style.setProperty('--tick', tick)
// Continue animation
requestAnimationFrame(step)
})
})
上述代码用于[requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame)
创建高效且优化的动画。在每个动画步骤中,我们计算“tick”并将其赋值给一个 CSS 属性。
如果我们保持这种状态,它会一直运行下去。所以,我们来添加一个“结束条件”来结束动画。当动画达到 1 秒(也就是 1000 毫秒)时,我们将停止它:
// index.js
button.addEventListener('click', (e) => {
// ...
requestAnimationFrame(function step(now) {
const tick = Math.floor(now - start)
button.style.setProperty('--tick', tick)
// Stop the animation after 1 second
if (tick > 1000) {
button.style.setProperty('--tick', 0)
return
}
// Continue animation
requestAnimationFrame(step)
})
})
这就是逻辑!
油漆工作坊
让我们使用 Paint API 来制作实际的涟漪效果。
这种效果应该放入一个单独的文件中,我们将其称为ripple.js
。
让我们首先检索刚刚定义的 CSS 属性:
// ripple.js
registerPaint('ripple', class {
static get inputProperties() {
return ['--x', '--y', '--tick']
}
})
接下来,我们将使用Canvas API在按钮的背景上绘制一个圆圈:
// ripple.js
registerPaint('ripple', class {
//...
paint(ctx, {width}, props) {
// Retrieve props
const x = parseFloat(props.get('--x').toString())
const y = parseFloat(props.get('--y').toString())
let tick = parseFloat(props.get('--tick').toString())
// Clamp tick in [0, 1000]
if (tick < 0) tick = 0
if (tick > 1000) tick = 1000
// Draw ripple
const rippleColor = 'rgba(255,255,255,0.54)'
ctx.fillStyle = rippleColor
ctx.globalAlpha = 1 - tick/1000
ctx.arc(
x, y, // center
width * tick/1000, // radius
0, 2 * Math.PI // full circle
)
ctx.fill()
}
})
注册绘画作品
返回到您的index.js
文件,并添加以下代码:
// index.js
if ('paintWorklet' in CSS) {
CSS.paintWorklet.addModule('ripple.js')
}
它会首先检查是否支持 CSS Paint API,然后才会链接涟漪效果。
大功告成!剩下的就是使用这个效果了。所以,将以下代码添加到你的 CSS 中:
button {
background-color: #0d1117;
background-image: paint(ripple);
}
要深入了解 CSS Paint API,请阅读Adrian Bece 的这篇精彩文章。
显示原生共享菜单
网络上有太多内容我们可能想与他人分享:
- 链接,
- 图像,
- 段落。
并且这个名单永远没有尽头。
通常,开发人员会实现自己的共享系统,并链接到 Twitter、Facebook 和其他社交媒体网站。
然而,这些组件与原生组件相比总是存在不足,因为原生组件具有大量的选项:
- 与联系人分享,
- 与其他应用程序共享,
- 通过蓝牙共享,
- 复制到剪贴板。
并且这个名单永远没有尽头。
这些原生共享菜单曾经是原生应用程序独有的,但有了Web Share API,这一事实不再成立。
浏览器兼容性在移动浏览器中非常出色,但在桌面版 Firefox 中则有点困难。
请在上面的 Codepen 中亲自尝试一下,如果您的设备不支持它,它看起来会像这样:
共享菜单包含许多选项,包括 Gmail、Messages、Reddit 和 LinkedIn。
分享网址
要查找的方法是navigator.share
。它需要一个包含标题、文本字符串和 URL 的对象。
const shareData = {
title: 'Smashing Magazine',
text: 'Smashing Magazine — For Web Designers And Developers',
url: 'https://www.smashingmagazine.com/'
}
await navigator.share(shareData)
请注意,此功能受瞬态激活保护,这意味着它需要 UI 事件(如单击)才能被处理。
将文本复制到剪贴板
剪贴板是当今计算机中最被低估的功能之一。如果没有Ctrl+C
Stackoverflow 上那些不断出现的 'ing 代码,我们开发者还能活下去吗?我表示怀疑。
剪贴板的作用是将数字信息从A点移动到B点。唯一的替代方案是手动重写内容,这很容易出错。现代剪贴板还可以复制图像和其他形式的媒体。
随着剪贴板 API的出现,开发者可以通过编程方式将重要信息复制到用户的剪贴板,从而向用户展现用户体验的美感。从 MDN 网站的代码到 Twitter,这项功能随处可见。唯独 Stackoverflow 缺少这项功能,而且理由充分。
浏览器兼容性也很好,当然 IE 除外。
使用剪贴板 API
复制文本非常简单:
await navigator.clipboard.writeText('Howdy, partner!')
阅读起来也同样简单:
const text = await navigator.clipboard.readText()
共享选定的文本
博客上的选定文本,其顶部有共享工具提示。
Medium 等多个博客允许用户轻松地与其他社交平台分享选定的文本。
作为一项非常有用的功能,它鼓励内容共享,从而使博客规模大大扩大。
我们已经在上一节中了解了如何调用本机共享菜单,因此我们只关注文本选择。
此外,我们不会看到如何在选定文本上方添加工具提示,但我们将深入研究使用Selection API来检索选定的文本部分,因为整篇文章都是关于 API 及其用例的。
而且无需担心浏览器兼容性,因为它非常完美!
获取选定的文本
这是一件非常容易的事情:
const text = window.getSelection().toString()
就是这样!现在,参考上一节关于 Web 共享 API 的内容,弹出一个操作系统定义的共享菜单,让你的用户尽情享受吧!
当用户离开标签页时更改标题
当用户离开标签时,网站标题从“购物网站”更改为“请停留”。
网站可以通过页面可见性 API判断其是否正在被查看。
虽然我不主张使用 Page Visibility API 通过烦人的消息来吸引用户的注意力,但它有许多积极的用例:
- 显示新通知,
- 报告参与度分析,
- 暂停视频和音频,
- 停止图片轮播。
浏览器兼容性不是问题。
检测页面可见性
我们可以通过以下代码随时获取页面的可见性状态:
document.visibilityState // 'visible' or 'hidden'
但实际用例需要监听事件并分别改变某些行为。
不幸的是,事件名称因浏览器而异,所以我们必须执行以下操作:
let hidden;
let visibilityChange;
if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
hidden = "hidden";
visibilityChange = "visibilitychange";
} else if (typeof document.msHidden !== "undefined") {
hidden = "msHidden";
visibilityChange = "msvisibilitychange";
} else if (typeof document.webkitHidden !== "undefined") {
hidden = "webkitHidden";
visibilityChange = "webkitvisibilitychange";
}
然后我们可以监听页面可见性事件,如下所示:
document.addEventListener(visibilityChange, handleVisibilityChange);
function handleVisibilityChange() {
if (document[hidden]) {
// page is hidden
} else {
// page is visible
}
}
为了演示的目的,我们只需更改文档标题:
function handleVisibilityChange() {
if (document[hidden]) {
document.title = 'Please stay!!!'
} else {
document.title = 'Shopping website'
}
}
请注意,我不建议这样做,因为这只会让用户感到烦恼,并且是道德网页设计中的一种根本性的不当行为。
结论
如今,Web API 正在弥合 Web 应用程序和本机应用程序之间的差距。
网络正开始对 App Store 和 Google Play Store 所造成的垄断构成真正的威胁,而且这种威胁丝毫没有停止的迹象。欢迎在下面的评论区讨论!
还有许多 API 我们尚未探索,其中一些 API 甚至能实现令人难以置信的功能,例如扫描条形码,甚至识别语音!敬请期待第二部分!❤️
荣誉提名
如果不提及另一组很少使用但却有许多有趣且实用的用例的 API,那将是一种遗憾:
喜欢免费赠品吗?🤑
我制作了一套包含 100 个免费悬停动画的包。立即获取,分享,随心所欲地使用。它永远属于你!❤️
![[已删除用户] 图片](/upload/9-aigb.png)