网站首页 > 技术文章 正文
为什么浏览器内置的API你还在用某个臃肿的Javascript库呢?用内置的API有什么好处呢?
Web平台经历了巨大演进,引入了强大的原生API,不再需要臃肿的JavaScript库。现代浏览器现已支持以往需要第三方依赖的复杂功能,从而带来更快的加载速度、更好的性能表现和更小的代码体积。
在分析了数百个生产环境应用后,我总结了10个最具影响力的原生Web API,它们能替代流行JavaScript库,同时提供更优的性能和用户体验。
1.Web Components API:替代React/Vue实现简单组件
无需框架依赖即可创建可复用组件
class CustomButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
this.addEventListener('click', this.handleClick);
}
disconnectedCallback() {
this.removeEventListener('click', this.handleClick);
}
static get observedAttributes() {
return ['variant', 'disabled'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this.render();
}
}
render() {
const variant = this.getAttribute('variant') || 'primary';
const disabled = this.hasAttribute('disabled');
this.shadowRoot.innerHTML = `
<style>
button {
padding: 12px 24px;
border: none;
border-radius: 6px;
cursor: pointer;
font-family: inherit;
font-size: 14px;
transition: all 0.2s ease;
}
.primary {
background: #007bff;
color: white;
}
.secondary {
background: #6c757d;
color: white;
}
button:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style>
<button class="${variant}" ${disabled ? 'disabled' : ''}>
<slot></slot>
</button>
`;
}
handleClick = (event) => {
if (!this.hasAttribute('disabled')) {
this.dispatchEvent(new CustomEvent('custom-click', {
detail: { originalEvent: event },
bubbles: true
}));
}
}
}
customElements.define('custom-button', CustomButton);
// 用法
// <custom-button variant="primary">点击我</custom-button>
性能优势:相比React组件,简单UI元素代码体积减少80%
2.Intersection Observer API:替代滚动事件库
高效检测元素可见性,无需滚动事件监听器
class LazyImageLoader {
constructor(options = {}) {
this.options = {
rootMargin: '50px 0px',
threshold: 0.1,
...options
};
this.observer = new IntersectionObserver(
this.handleIntersection.bind(this),
this.options
);
this.loadingImages = new Set();
}
observe(img) {
if (img.dataset.src && !this.loadingImages.has(img)) {
this.observer.observe(img);
}
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadImage(entry.target);
this.observer.unobserve(entry.target);
}
});
}
async loadImage(img) {
this.loadingImages.add(img);
try {
// 预加载图片
const imageLoader = new Image();
imageLoader.src = img.dataset.src;
await new Promise((resolve, reject) => {
imageLoader.onload = resolve;
imageLoader.onerror = reject;
});
// 为加载图片实施丝滑过度
img.style.opacity = '0';
img.src = img.dataset.src;
img.onload = () => {
img.style.transition = 'opacity 0.3s ease';
img.style.opacity = '1';
img.classList.add('loaded');
};
} catch (error) {
console.error('Failed to load image:', error);
img.classList.add('error');
} finally {
this.loadingImages.delete(img);
}
}
disconnect() {
this.observer.disconnect();
this.loadingImages.clear();
}
}
// 无限滚动实施
class InfiniteScroll {
constructor(container, loadMore, options = {}) {
this.container = container;
this.loadMore = loadMore;
this.loading = false;
this.sentinel = document.createElement('div');
this.sentinel.style.height = '1px';
container.appendChild(this.sentinel);
this.observer = new IntersectionObserver(
this.handleIntersection.bind(this),
{ rootMargin: '100px', ...options }
);
this.observer.observe(this.sentinel);
}
async handleIntersection(entries) {
const entry = entries[0];
if (entry.isIntersecting && !this.loading) {
this.loading = true;
try {
const hasMore = await this.loadMore();
if (!hasMore) {
this.observer.disconnect();
}
} catch (error) {
console.error('Failed to load more content:', error);
} finally {
this.loading = false;
}
}
}
}
3.Resize Observer API:替代窗口缩放库
高效监控元素尺寸变化
class ResponsiveComponent {
constructor(element) {
this.element = element; // 需要响应式处理的DOM元素
// 定义响应式断点(单位:像素)
this.breakpoints = {
small: 320, // 小屏幕(手机竖屏)
medium: 768, // 中等屏幕(平板)
large: 1024, // 大屏幕(笔记本)
xlarge: 1440 // 超大屏幕(桌面显示器)
};
// 创建ResizeObserver实例监听元素尺寸变化
this.resizeObserver = new ResizeObserver(
this.handleResize.bind(this) // 绑定this上下文
);
// 开始观察目标元素
this.resizeObserver.observe(element);
}
// 处理元素尺寸变化的回调函数
handleResize(entries) {
entries.forEach(entry => {
// 从内容矩形中解构获取当前宽度和高度
const { width, height } = entry.contentRect;
// 根据当前宽度获取对应的断点类型
const currentBreakpoint = this.getBreakpoint(width);
// 更新CSS自定义属性(可在CSS中通过var()引用)
this.element.style.setProperty('--element-width', `${width}px`);
this.element.style.setProperty('--element-height', `${height}px`);
// 更新data-size属性(用于CSS选择器定位)
this.element.dataset.size = currentBreakpoint;
// 触发自定义事件(允许外部监听响应式变化)
this.element.dispatchEvent(new CustomEvent('element-resize', {
detail: {
width, // 当前元素宽度
height, // 当前元素高度
breakpoint: currentBreakpoint // 当前断点类型
}
}));
// 调用断点变化处理函数
this.handleBreakpointChange(currentBreakpoint, width, height);
});
}
// 根据宽度确定当前断点类型
getBreakpoint(width) {
if (width < this.breakpoints.small) return 'xsmall'; // 超小屏幕
if (width < this.breakpoints.medium) return 'small'; // 小屏幕
if (width < this.breakpoints.large) return 'medium'; // 中等屏幕
if (width < this.breakpoints.xlarge) return 'large'; // 大屏幕
return 'xlarge'; // 超大屏幕
}
// 处理不同断点的响应式行为
handleBreakpointChange(breakpoint, width, height) {
// 首先移除所有可能存在的布局类
this.element.classList.remove(
'mobile-layout',
'tablet-layout',
'desktop-layout'
);
// 根据断点类型添加对应的布局类
switch (breakpoint) {
case 'small':
this.element.classList.add('mobile-layout'); // 移动端布局
break;
case 'medium':
this.element.classList.add('tablet-layout'); // 平板布局
break;
default:
this.element.classList.add('desktop-layout'); // 桌面端布局
}
}
// 停止监听并清理资源
disconnect() {
this.resizeObserver.disconnect(); // 停止所有观察
}
}
4.支持流式传输的Fetch API:替代Axios等HTTP库
用原生能力处理HTTP请求
class AdvancedHTTP {
constructor(baseURL = '', options = {}) {
this.baseURL = baseURL;
this.defaultOptions = {
headers: {
'Content-Type': 'application/json'
},
...options
};
// 初始化拦截器对象,包含请求拦截器和响应拦截器数组
this.interceptors = {
request: [],
response: []
};
}
addRequestInterceptor(interceptor) {
// 添加请求拦截器到拦截器数组
this.interceptors.request.push(interceptor);
}
addResponseInterceptor(interceptor) {
// 添加响应拦截器到拦截器数组
this.interceptors.response.push(interceptor);
}
async request(url, options = {}) {
// 合并默认配置和传入的配置选项
let config = {
...this.defaultOptions,
...options,
headers: {
...this.defaultOptions.headers,
...options.headers
}
};
// 应用所有请求拦截器,按顺序执行
for (const interceptor of this.interceptors.request) {
config = await interceptor(config);
}
try {
// 拼接完整的请求URL
const fullURL = this.baseURL + url;
// 发送fetch请求
let response = await fetch(fullURL, config);
// 应用所有响应拦截器,按顺序执行
for (const interceptor of this.interceptors.response) {
response = await interceptor(response);
}
return response;
} catch (error) {
// 请求失败时抛出错误
throw new Error(`HTTP请求失败: ${error.message}`);
}
}
// 流式响应处理
async streamingRequest(url, onData, options = {}) {
// 发送请求获取响应
const response = await this.request(url, {
...options,
headers: {
...options.headers,
'Accept': 'text/plain'
}
});
// 检查响应是否支持流式读取
if (!response.body) {
throw new Error('不支持流式传输');
}
// 获取响应体的读取器
const reader = response.body.getReader();
// 创建文本解码器
const decoder = new TextDecoder();
try {
// 循环读取流数据
while (true) {
// 读取一块数据
const { done, value } = await reader.read();
if (done) break; // 如果读取完成则退出循环
// 解码数据块
const chunk = decoder.decode(value, { stream: true });
// 调用回调函数处理数据块
await onData(chunk);
}
} finally {
// 释放读取器锁
reader.releaseLock();
}
}
// 带进度跟踪的文件上传
async uploadWithProgress(url, file, onProgress) {
return new Promise((resolve, reject) => {
// 创建XMLHttpRequest对象
const xhr = new XMLHttpRequest();
// 监听上传进度事件
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
// 计算上传进度百分比
const percentComplete = (event.loaded / event.total) * 100;
// 调用进度回调函数
onProgress(percentComplete);
}
});
// 监听加载完成事件
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
// 请求成功,解析响应数据
resolve(JSON.parse(xhr.responseText));
} else {
// 请求失败,抛出错误
reject(new Error(`上传失败: ${xhr.statusText}`));
}
});
// 监听错误事件
xhr.addEventListener('error', () => {
// 发生错误,抛出错误
reject(new Error('上传失败'));
});
// 创建FormData对象并添加文件
const formData = new FormData();
formData.append('file', file);
// 打开连接并发送请求
xhr.open('POST', this.baseURL + url);
xhr.send(formData);
});
}
// 便捷的GET请求方法
get(url, options) {
return this.request(url, { ...options, method: 'GET' });
}
// 便捷的POST请求方法
post(url, data, options) {
return this.request(url, {
...options,
method: 'POST',
body: JSON.stringify(data)
});
}
// 便捷的PUT请求方法
put(url, data, options) {
return this.request(url, {
...options,
method: 'PUT',
body: JSON.stringify(data)
});
}
// 便捷的DELETE请求方法
delete(url, options) {
return this.request(url, { ...options, method: 'DELETE' });
}
}
5.Web Animations API:替代动画库
无需外部依赖创建流畅动画
class AnimationManager {
constructor() {
// 使用Map存储单个动画实例,键为自动生成的ID,值为Animation对象
this.animations = new Map();
// 使用Map存储动画序列,键为自动生成的ID,值为Animation对象数组
this.sequences = new Map();
}
// 基础动画方法,支持缓动效果
animate(element, keyframes, options = {}) {
// 创建动画实例,设置默认参数:300ms时长、ease-out缓动、forwards填充模式
const animation = element.animate(keyframes, {
duration: 300,
easing: 'ease-out',
fill: 'forwards',
...options // 合并传入的自定义选项
});
// 生成唯一ID:元素ID(若无则用'element') + 时间戳
const id = `${element.id || 'element'}_${Date.now()}`;
// 将动画实例存入Map
this.animations.set(id, animation);
// 动画结束时自动清理Map中的记录
animation.addEventListener('finish', () => {
this.animations.delete(id);
});
return animation; // 返回动画实例以便链式调用
}
// 淡入动画效果
fadeIn(element, duration = 300) {
// 定义关键帧:从透明且下移20px到完全不透明且回到原位
return this.animate(element, [
{ opacity: 0, transform: 'translateY(20px)' },
{ opacity: 1, transform: 'translateY(0)' }
], { duration }); // 应用传入的时长参数
}
// 滑动动画效果
slideIn(element, direction = 'left', duration = 300) {
// 定义不同方向的滑动起始和结束位置
const transforms = {
left: ['translateX(-100%)', 'translateX(0)'], // 从左侧滑入
right: ['translateX(100%)', 'translateX(0)'], // 从右侧滑入
up: ['translateY(-100%)', 'translateY(0)'], // 从上方滑入
down: ['translateY(100%)', 'translateY(0)'] // 从下方滑入
};
// 使用对应方向的transform关键帧创建动画
return this.animate(element, [
{ transform: transforms[direction][0] },
{ transform: transforms[direction][1] }
], { duration });
}
// 弹性动画效果
spring(element, keyframes, options = {}) {
// 定义弹性缓动曲线
const springEasing = 'cubic-bezier(0.68, -0.55, 0.265, 1.55)';
// 创建带有弹性效果的动画
return this.animate(element, keyframes, {
duration: 600, // 默认600ms时长
easing: springEasing,// 应用弹性缓动
...options // 合并其他自定义选项
});
}
// 动画序列执行
async sequence(animations) {
// 生成唯一序列ID
const id = `sequence_${Date.now()}`;
const sequence = []; // 存储序列中的动画实例
// 遍历动画配置数组
for (const animationConfig of animations) {
const { element, keyframes, options, delay = 0 } = animationConfig;
// 如果有延迟设置,等待指定时间
if (delay > 0) {
await new Promise(resolve => setTimeout(resolve, delay));
}
// 创建并执行动画
const animation = this.animate(element, keyframes, options);
sequence.push(animation); // 将动画加入序列
// 如果配置要求等待动画完成(默认行为),则等待animation.finished Promise
if (options?.wait !== false) {
await animation.finished;
}
}
// 将完成的序列存入Map
this.sequences.set(id, sequence);
return sequence; // 返回序列数组以便外部控制
}
// 交错动画效果
stagger(elements, keyframes, options = {}) {
// 从选项中提取交错延迟时间(默认100ms),其余选项保留
const { staggerDelay = 100, ...animOptions } = options;
// 为每个元素创建带动画的Promise,按索引计算延迟时间
return elements.map((element, index) => {
return this.animate(element, keyframes, {
...animOptions, // 合并基础动画选项
delay: index * staggerDelay // 每个元素递增延迟
});
});
}
// 视差滚动效果
parallax(elements, scrollContainer = window) {
// 定义更新视差效果的函数
const updateParallax = () => {
// 获取当前滚动位置(兼容window和自定义容器)
const scrollTop = scrollContainer === window
? window.pageYOffset
: scrollContainer.scrollTop;
// 遍历所有需要视差效果的元素
elements.forEach(({ element, speed = 0.5 }) => {
// 获取元素位置信息
const rect = element.getBoundingClientRect();
// 计算偏移量 = 滚动距离 * 速度系数
const offset = scrollTop * speed;
// 应用transform实现位移效果
element.style.transform = `translateY(${offset}px)`;
});
};
// 监听滚动事件(使用passive模式提升性能)
scrollContainer.addEventListener('scroll', updateParallax, { passive: true });
// 初始化时立即执行一次
updateParallax();
// 返回清理函数,用于移除事件监听
return () => {
scrollContainer.removeEventListener('scroll', updateParallax);
};
}
// 停止所有动画
stopAll() {
// 取消所有单个动画
this.animations.forEach(animation => animation.cancel());
// 取消所有序列中的动画
this.sequences.forEach(sequence =>
sequence.forEach(animation => animation.cancel())
);
// 清空两个Map
this.animations.clear();
this.sequences.clear();
}
}
6.Broadcast Channel API:替代消息通信库
无需第三方依赖实现跨标签页通信
class CrossTabCommunication {
constructor(channelName = 'app-channel') {
// 创建BroadcastChannel实例,用于跨标签页通信
this.channel = new BroadcastChannel(channelName);
// 使用Map存储消息类型与处理函数的映射关系
this.handlers = new Map();
// 监听channel的消息事件
this.channel.addEventListener('message', this.handleMessage.bind(this));
// 处理页面可见性变化,用于标签页状态同步
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
// 当前标签页隐藏时广播通知
this.broadcast('tab-hidden', { tabId: this.tabId });
} else {
// 当前标签页变为可见时广播通知
this.broadcast('tab-visible', { tabId: this.tabId });
}
});
// 生成唯一的标签页ID,结合时间戳和随机字符串确保唯一性
this.tabId = `tab_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// 广播当前标签页的连接通知
this.broadcast('tab-connected', { tabId: this.tabId });
}
// 向所有其他标签页发送消息
broadcast(type, data = {}) {
const message = {
type, // 消息类型
data, // 消息数据
timestamp: Date.now(), // 时间戳
sender: this.tabId // 发送者标签页ID
};
this.channel.postMessage(message); // 通过BroadcastChannel发送消息
}
// 监听特定类型的消息
on(type, handler) {
// 如果该消息类型尚未注册处理函数,初始化一个Set集合
if (!this.handlers.has(type)) {
this.handlers.set(type, new Set());
}
// 将处理函数添加到对应消息类型的集合中
this.handlers.get(type).add(handler);
// 返回取消订阅的函数,便于后续移除监听
return () => {
const handlers = this.handlers.get(type);
if (handlers) {
handlers.delete(handler); // 移除指定处理函数
if (handlers.size === 0) {
this.handlers.delete(type); // 如果没有处理函数了,删除该消息类型的注册
}
}
};
}
// 处理接收到的消息
handleMessage(event) {
// 从事件中解构出消息类型、数据和发送者ID
const { type, data, sender } = event.data;
// 忽略自己发送的消息,避免循环处理
if (sender === this.tabId) return;
// 获取该消息类型的所有处理函数
const handlers = this.handlers.get(type);
if (handlers) {
// 依次执行所有处理函数
handlers.forEach(handler => {
try {
handler(data, sender); // 调用处理函数并传入数据和发送者ID
} catch (error) {
console.error('消息处理函数出错:', error); // 捕获并打印处理函数中的错误
}
});
}
}
// 跨标签页同步状态
syncState(key, value) {
// 广播状态同步消息
this.broadcast('state-sync', { key, value });
}
// 请求其他标签页的状态
requestState(key) {
return new Promise((resolve) => {
// 设置超时处理,1秒后自动resolve为null
const timeout = setTimeout(() => {
unsubscribe(); // 取消订阅
resolve(null); // 解析为null表示超时
}, 1000);
// 定义取消订阅函数
const unsubscribe = this.on('state-response', ({ key: responseKey, value }) => {
if (responseKey === key) {
clearTimeout(timeout); // 收到响应后清除超时
unsubscribe(); // 取消订阅
resolve(value); // 解析响应值
}
});
// 广播状态请求消息
this.broadcast('state-request', { key });
});
}
// 协调单标签页操作(获取锁)
async acquireLock(lockName, timeout = 5000) {
return new Promise((resolve, reject) => {
let acquired = false; // 标记是否成功获取锁
// 设置获取锁的超时处理
const timeoutId = setTimeout(() => {
if (!acquired) {
cleanup(); // 清理监听
reject(new Error('获取锁超时')); // 超时后拒绝Promise
}
}, timeout);
// 定义清理函数
const cleanup = this.on('lock-response', ({ lock, holder }) => {
if (lock === lockName) {
if (holder === null) {
// 如果锁未被持有,则成功获取
acquired = true;
clearTimeout(timeoutId); // 清除超时
cleanup(); // 清理监听
// 声明自己持有锁
this.broadcast('lock-claimed', { lock: lockName, holder: this.tabId });
// 返回释放锁的函数
resolve(() => this.releaseLock(lockName));
}
}
});
// 广播锁请求消息
this.broadcast('lock-request', { lock: lockName });
});
}
// 释放锁
releaseLock(lockName) {
this.broadcast('lock-released', { lock: lockName, holder: this.tabId });
}
// 清理资源
close() {
// 广播标签页断开连接通知
this.broadcast('tab-disconnected', { tabId: this.tabId });
// 关闭BroadcastChannel
this.channel.close();
// 清空所有消息处理函数
this.handlers.clear();
}
}
7.文件系统访问API:替代文件上传库
用浏览器原生能力处理文件操作
class FileManager {
constructor() {
// 使用Map存储文件句柄,键为文件名,值为FileSystemFileHandle对象
this.fileHandles = new Map();
// 使用Map存储目录句柄,键为目录名,值为FileSystemDirectoryHandle对象
this.directoryHandles = new Map();
}
// 检查浏览器是否支持文件系统访问API
static isSupported() {
return 'showOpenFilePicker' in window &&
'showSaveFilePicker' in window &&
'showDirectoryPicker' in window;
}
// 打开单个文件选择器
async openFile(options = {}) {
try {
// 调用文件选择器API,默认配置为单选模式
const [fileHandle] = await window.showOpenFilePicker({
multiple: false, // 禁止多选
excludeAcceptAllOption: false, // 显示"所有文件"选项
types: [{
description: 'All files',
accept: { '*/*': [] } // 接受所有文件类型
}],
...options // 合并自定义选项
});
// 获取文件对象
const file = await fileHandle.getFile();
// 存储文件句柄
this.fileHandles.set(file.name, fileHandle);
// 返回文件对象和句柄
return { file, handle: fileHandle };
} catch (error) {
// 忽略用户取消操作产生的错误
if (error.name !== 'AbortError') {
throw error;
}
return null;
}
}
// 打开多个文件选择器
async openFiles(options = {}) {
try {
// 启用多选模式
const fileHandles = await window.showOpenFilePicker({
multiple: true,
...options
});
// 并行获取所有文件对象
const files = await Promise.all(
fileHandles.map(async (handle) => {
const file = await handle.getFile();
this.fileHandles.set(file.name, handle);
return { file, handle };
})
);
return files;
} catch (error) {
if (error.name !== 'AbortError') {
throw error;
}
return []; // 用户取消时返回空数组
}
}
// 保存文件
async saveFile(content, options = {}) {
try {
// 调用文件保存对话框
const fileHandle = await window.showSaveFilePicker({
suggestedName: 'untitled.txt', // 默认文件名
types: [{
description: 'Text files',
accept: { 'text/plain': ['.txt'] } // 默认保存为文本文件
}],
...options
});
// 获取可写流
const writable = await fileHandle.createWritable();
// 支持直接写入Blob或字符串内容
if (content instanceof Blob) {
await writable.write(content);
} else {
await writable.write(new Blob([content], { type: 'text/plain' }));
}
// 关闭流完成写入
await writable.close();
return fileHandle;
} catch (error) {
if (error.name !== 'AbortError') {
throw error;
}
return null;
}
}
// 打开目录选择器
async openDirectory(options = {}) {
try {
const directoryHandle = await window.showDirectoryPicker(options);
// 存储目录句柄
this.directoryHandles.set(directoryHandle.name, directoryHandle);
return directoryHandle;
} catch (error) {
if (error.name !== 'AbortError') {
throw error;
}
return null;
}
}
// 读取目录内容
async readDirectory(directoryHandle) {
const entries = [];
// 使用异步迭代器遍历目录项
for await (const [name, handle] of directoryHandle.entries()) {
entries.push({
name, // 条目名称
handle, // 文件/目录句柄
kind: handle.kind, // 类型标识
isDirectory: handle.kind === 'directory', // 是否为目录
isFile: handle.kind === 'file' // 是否为文件
});
}
return entries;
}
// 在指定目录中创建文件
async createFileInDirectory(directoryHandle, fileName, content) {
try {
// 获取文件句柄(自动创建)
const fileHandle = await directoryHandle.getFileHandle(fileName, {
create: true
});
// 写入内容
const writable = await fileHandle.createWritable();
await writable.write(content);
await writable.close();
return fileHandle;
} catch (error) {
throw new Error(`创建文件失败: ${error.message}`);
}
}
// 监听文件变化(实验性功能)
async watchFile(fileHandle, callback) {
let lastModified = null; // 记录最后修改时间
const check = async () => {
try {
const file = await fileHandle.getFile();
if (lastModified === null) {
// 首次检查,记录初始修改时间
lastModified = file.lastModified;
} else if (file.lastModified !== lastModified) {
// 检测到修改时间变化
lastModified = file.lastModified;
callback(file); // 触发回调
}
} catch (error) {
console.error('文件监听出错:', error);
}
};
// 每秒检查一次(可根据需要调整间隔)
const interval = setInterval(check, 1000);
// 返回清理函数用于停止监听
return () => clearInterval(interval);
}
// 设置拖放区域
setupDropZone(element, options = {}) {
const handleDrop = async (event) => {
event.preventDefault(); // 阻止默认行为
// 获取拖放项并转换为文件系统句柄
const items = Array.from(event.dataTransfer.items);
const entries = [];
for (const item of items) {
if (item.kind === 'file') {
const entry = await item.getAsFileSystemHandle();
entries.push(entry);
}
}
// 触发自定义回调
if (options.onDrop) {
options.onDrop(entries);
}
};
const handleDragOver = (event) => {
event.preventDefault(); // 必须阻止默认行为才能触发drop事件
if (options.onDragOver) {
options.onDragOver(event);
}
};
// 绑定事件监听器
element.addEventListener('drop', handleDrop);
element.addEventListener('dragover', handleDragOver);
// 返回清理函数用于移除监听
return () => {
element.removeEventListener('drop', handleDrop);
element.removeEventListener('dragover', handleDragOver);
};
}
}
8.支付请求API:替代支付处理库
原生处理支付流程
class PaymentManager {
constructor() {
// 初始化支持的支付方式(基础信用卡支付)
this.supportedMethods = [
{
supportedMethods: 'basic-card', // 支付方式标识
data: {
supportedNetworks: ['visa', 'mastercard', 'amex'], // 支持的卡组织
supportedTypes: ['debit', 'credit'] // 支持借记卡/信用卡
}
}
];
}
// 检查浏览器是否支持支付请求API
static isSupported() {
return 'PaymentRequest' in window;
}
// 创建支付请求
async createPaymentRequest(details, options = {}) {
if (!PaymentManager.isSupported()) {
throw new Error('当前浏览器不支持支付请求API');
}
// 默认支付详情(会被传入的details参数覆盖)
const paymentDetails = {
total: {
label: '总计', // 总金额标签
amount: { currency: 'USD', value: '0.00' } // 默认美元货币
},
displayItems: [], // 显示项目列表
...details // 合并自定义支付详情
};
// 默认支付选项(会被传入的options参数覆盖)
const paymentOptions = {
requestPayerName: true, // 要求提供付款人姓名
requestPayerEmail: true, // 要求提供付款人邮箱
requestPayerPhone: false, // 不要求提供电话
requestShipping: false, // 不要求配送地址
...options // 合并自定义选项
};
// 创建并返回PaymentRequest实例
return new PaymentRequest(
this.supportedMethods, // 支持的支付方式
paymentDetails, // 支付详情
paymentOptions // 支付选项
);
}
// 处理支付流程
async processPayment(details, options = {}) {
try {
// 创建支付请求
const request = await this.createPaymentRequest(details, options);
// 检查用户是否可以使用当前支付方式
const canMakePayment = await request.canMakePayment();
if (!canMakePayment) {
throw new Error('用户无法使用可用支付方式进行付款');
}
// 显示支付界面并获取用户响应
const response = await request.show();
// 验证支付信息(需实现自定义验证逻辑)
const isValid = await this.validatePayment(response);
if (isValid) {
// 支付成功,完成交易
await response.complete('success');
return {
success: true,
response, // 支付响应对象
paymentMethod: response.methodName, // 使用的支付方式
details: response.details // 支付详情
};
} else {
// 支付验证失败
await response.complete('fail');
return {
success: false,
error: '支付验证失败'
};
}
} catch (error) {
// 捕获并返回错误信息
return {
success: false,
error: error.message
};
}
}
// 更新配送选项(根据地址变化动态计算)
updateShippingOptions(request, shippingOptions) {
// 监听配送地址变化事件
request.addEventListener('shippingaddresschange', async (event) => {
const shippingAddress = request.shippingAddress;
// 根据地址计算配送选项
const options = await this.calculateShippingOptions(shippingAddress);
// 更新支付请求的配送选项
event.updateWith({
shippingOptions: options
});
});
}
// 计算配送选项和费用
async calculateShippingOptions(address) {
// 实现您的配送计算逻辑
// 基础配送选项
const baseShipping = {
id: 'standard', // 选项ID
label: '标准配送', // 显示标签
amount: { currency: 'USD', value: '5.00' } // 配送费用
};
// 加急配送选项
const expressShipping = {
id: 'express', // 选项ID
label: '加急配送', // 显示标签
amount: { currency: 'USD', value: '15.00' } // 配送费用
};
return [baseShipping, expressShipping];
}
// 验证支付信息(通常需要服务器端验证)
async validatePayment(response) {
// 实现您的支付验证逻辑
// 这里示例调用服务器API进行验证
try {
const validationResult = await fetch('/api/validate-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
methodName: response.methodName, // 支付方式
details: response.details, // 支付详情
payerName: response.payerName, // 付款人姓名
payerEmail: response.payerEmail // 付款人邮箱
})
});
// 返回验证结果(根据API响应状态)
return validationResult.ok;
} catch (error) {
console.error('支付验证错误:', error);
return false;
}
}
// 处理订阅支付
async createSubscription(planDetails, options = {}) {
// 构建订阅支付详情
const subscriptionDetails = {
total: {
label: `${planDetails.name} - 月付`, // 显示标签
amount: { currency: 'USD', value: planDetails.price } // 订阅金额
},
displayItems: [
{
label: planDetails.name, // 订阅计划名称
amount: { currency: 'USD', value: planDetails.price } // 计划价格
}
]
};
// 处理支付(强制要求提供邮箱)
const result = await this.processPayment(subscriptionDetails, {
...options,
requestPayerEmail: true
});
// 支付成功后存储订阅信息
if (result.success) {
await this.storeSubscription({
planId: planDetails.id, // 计划ID
payerEmail: result.response.payerEmail, // 付款人邮箱
paymentMethod: result.paymentMethod // 支付方式
});
}
return result;
}
// 存储订阅信息到服务器
async storeSubscription(subscriptionData) {
// 实现您的订阅存储逻辑
return fetch('/api/subscriptions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscriptionData) // 订阅数据
});
}
}
9.屏幕捕捉API:替代屏幕录制库
原生实现屏幕内容捕获
class ScreenCaptureManager {
constructor() {
this.mediaRecorder = null; // 用于存储媒体录制器实例
this.recordedChunks = []; // 存储录制视频的数据块
this.stream = null; // 存储屏幕捕获的媒体流
}
// 检查浏览器是否支持屏幕捕获API
static isSupported() {
return 'getDisplayMedia' in navigator.mediaDevices;
}
// 开始屏幕捕获
async startCapture(options = {}) {
if (!ScreenCaptureManager.isSupported()) {
throw new Error('当前浏览器不支持屏幕捕获API');
}
try {
// 配置屏幕捕获选项
const displayMediaOptions = {
video: {
cursor: 'always', // 始终显示鼠标光标
displaySurface: 'browser', // 默认捕获浏览器窗口
...options.video // 合并自定义视频选项
},
audio: {
echoCancellation: true, // 启用回声消除
noiseSuppression: true, // 启用噪音抑制
sampleRate: 44100, // 设置音频采样率
...options.audio // 合并自定义音频选项
}
};
// 获取屏幕共享媒体流
this.stream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
// 监听视频轨道结束事件(用户停止共享时触发)
this.stream.getVideoTracks()[0].addEventListener('ended', () => {
this.stopCapture();
});
return this.stream;
} catch (error) {
throw new Error(`屏幕捕获启动失败: ${error.message}`);
}
}
// 开始录制屏幕
startRecording(options = {}) {
if (!this.stream) {
throw new Error('没有活动的屏幕捕获流');
}
// 配置录制选项
const recordingOptions = {
mimeType: 'video/webm;codecs=vp9', // 默认使用VP9编码
videoBitsPerSecond: 2500000, // 设置视频比特率
...options // 合并自定义选项
};
// 支持的视频格式列表
const supportedTypes = [
'video/webm;codecs=vp9',
'video/webm;codecs=vp8',
'video/webm',
'video/mp4'
];
// 检测浏览器实际支持的视频格式
const mimeType = supportedTypes.find(type =>
MediaRecorder.isTypeSupported(type)
) || 'video/webm'; // 默认回退到webm格式
// 创建媒体录制器实例
this.mediaRecorder = new MediaRecorder(this.stream, {
...recordingOptions,
mimeType // 使用检测到的支持格式
});
// 初始化录制数据块数组
this.recordedChunks = [];
// 数据可用事件处理
this.mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
this.recordedChunks.push(event.data); // 存储视频数据块
}
};
// 录制停止事件处理
this.mediaRecorder.onstop = () => {
// 将所有数据块合并为一个Blob对象
const blob = new Blob(this.recordedChunks, { type: mimeType });
this.onRecordingComplete(blob); // 处理录制完成的视频
};
// 开始录制,每1000毫秒收集一次数据
this.mediaRecorder.start(1000);
return this.mediaRecorder;
}
// 停止录制
stopRecording() {
// 如果录制器存在且处于活动状态
if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
this.mediaRecorder.stop(); // 停止录制
}
}
// 停止屏幕捕获
stopCapture() {
// 停止所有媒体轨道
if (this.stream) {
this.stream.getTracks().forEach(track => track.stop());
this.stream = null;
}
// 停止录制器
if (this.mediaRecorder) {
this.stopRecording();
}
}
// 处理录制完成的视频
onRecordingComplete(blob) {
// 创建视频下载链接
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none'; // 隐藏下载链接
a.href = url;
a.download = `screen-recording-${Date.now()}.webm`; // 设置带时间戳的文件名
document.body.appendChild(a);
a.click(); // 触发下载
// 清理DOM和释放内存
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url); // 释放对象URL
}, 100);
}
// 截取屏幕画面
async takeScreenshot() {
// 如果没有活动流,则自动启动无音频的屏幕捕获
if (!this.stream) {
await this.startCapture({ audio: false });
}
// 创建视频元素用于捕获画面
const video = document.createElement('video');
video.srcObject = this.stream;
video.play(); // 必须调用play()才能获取有效画面
return new Promise((resolve) => {
// 等待视频元数据加载完成
video.addEventListener('loadedmetadata', () => {
// 创建画布用于截图
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 设置画布尺寸与视频一致
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
// 将视频画面绘制到画布
ctx.drawImage(video, 0, 0);
// 将画布转换为PNG格式的Blob对象
canvas.toBlob((blob) => {
resolve(blob); // 返回截图数据
}, 'image/png');
});
});
}
// 创建实时预览窗口
createPreview(container) {
if (!this.stream) {
throw new Error('没有活动的屏幕捕获流');
}
// 创建并配置预览视频元素
const video = document.createElement('video');
video.srcObject = this.stream;
video.autoplay = true; // 自动播放
video.muted = true; // 静音避免回声
video.style.width = '100%';
video.style.height = 'auto';
// 将预览添加到指定容器
container.appendChild(video);
return video;
}
// 启用画中画模式
async enablePictureInPicture(video) {
// 检查浏览器是否支持画中画
if ('pictureInPictureEnabled' in document) {
try {
await video.requestPictureInPicture(); // 请求进入画中画模式
} catch (error) {
console.error('启用画中画失败:', error);
}
}
}
}
10.地理定位API:替代位置服务库
原生获取设备位置信息
class LocationManager {
constructor(options = {}) {
// 默认配置选项(可被传入的options覆盖)
this.options = {
enableHighAccuracy: true, // 启用高精度定位
timeout: 10000, // 超时时间(10秒)
maximumAge: 300000, // 位置缓存有效期(5分钟)
...options // 合并自定义配置
};
this.watchId = null; // 位置监听器ID
this.lastKnownPosition = null; // 最后已知位置
this.listeners = new Set(); // 使用Set存储监听回调函数
}
// 检查浏览器是否支持地理定位API
static isSupported() {
return 'geolocation' in navigator;
}
// 获取当前位置(单次请求)
async getCurrentPosition(options = {}) {
if (!LocationManager.isSupported()) {
throw new Error('浏览器不支持地理定位功能');
}
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(
(position) => {
this.lastKnownPosition = position; // 缓存最新位置
resolve(this.formatPosition(position)); // 返回格式化后的位置数据
},
(error) => {
reject(this.handleError(error)); // 处理并返回错误信息
},
{ ...this.options, ...options } // 合并配置选项
);
});
}
// 持续监听位置变化
startWatching(callback, options = {}) {
if (!LocationManager.isSupported()) {
throw new Error('浏览器不支持地理定位功能');
}
// 添加回调到监听器集合
this.listeners.add(callback);
// 如果尚未启动监听,则创建监听器
if (!this.watchId) {
this.watchId = navigator.geolocation.watchPosition(
(position) => {
this.lastKnownPosition = position; // 更新最后已知位置
const formattedPosition = this.formatPosition(position); // 格式化位置数据
// 通知所有监听者
this.listeners.forEach(listener => {
try {
listener(formattedPosition);
} catch (error) {
console.error('位置监听回调出错:', error);
}
});
},
(error) => {
const formattedError = this.handleError(error); // 格式化错误信息
// 通知所有监听者(携带错误)
this.listeners.forEach(listener => {
try {
listener(null, formattedError);
} catch (error) {
console.error('错误监听回调出错:', error);
}
});
},
{ ...this.options, ...options } // 合并配置选项
);
}
// 返回取消监听的函数
return () => {
this.listeners.delete(callback); // 移除当前回调
// 如果没有监听者了,停止位置监听
if (this.listeners.size === 0) {
this.stopWatching();
}
};
}
// 停止监听位置变化
stopWatching() {
if (this.watchId) {
navigator.geolocation.clearWatch(this.watchId); // 清除监听器
this.watchId = null;
}
this.listeners.clear(); // 清空所有监听回调
}
// 格式化位置数据为简洁对象
formatPosition(position) {
return {
latitude: position.coords.latitude, // 纬度
longitude: position.coords.longitude, // 经度
accuracy: position.coords.accuracy, // 水平精度(米)
altitude: position.coords.altitude, // 海拔高度
altitudeAccuracy: position.coords.altitudeAccuracy, // 高度精度
heading: position.coords.heading, // 行进方向(度)
speed: position.coords.speed, // 速度(米/秒)
timestamp: position.timestamp // 时间戳
};
}
// 处理地理定位错误
handleError(error) {
const errorMessages = {
1: '用户拒绝位置访问权限', // PERMISSION_DENIED
2: '无法获取位置信息', // POSITION_UNAVAILABLE
3: '位置请求超时' // TIMEOUT
};
return {
code: error.code,
message: errorMessages[error.code] || '未知位置错误'
};
}
// 计算两点间距离(使用Haversine公式)
calculateDistance(pos1, pos2) {
const R = 6371; // 地球半径(公里)
const dLat = this.toRadians(pos2.latitude - pos1.latitude); // 纬度差转弧度
const dLon = this.toRadians(pos2.longitude - pos1.longitude); // 经度差转弧度
// Haversine公式计算
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(this.toRadians(pos1.latitude)) *
Math.cos(this.toRadians(pos2.latitude)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c; // 返回距离(公里)
}
// 角度转弧度
toRadians(degrees) {
return degrees * (Math.PI / 180);
}
// 反向地理编码(需要OpenCageData服务)
async reverseGeocode(latitude, longitude) {
try {
const response = await fetch(
`https://api.opencagedata.com/geocode/v1/json?q=${latitude}+${longitude}&key=YOUR_API_KEY`
);
const data = await response.json();
if (data.results && data.results.length > 0) {
return {
address: data.results[0].formatted, // 格式化地址
components: data.results[0].components // 地址组成部分
};
}
return null; // 无结果
} catch (error) {
console.error('反向地理编码失败:', error);
return null;
}
}
// 设置地理围栏(进入/离开指定区域触发回调)
async setupGeofence(center, radius, callback) {
// 启动位置监听
const unsubscribe = this.startWatching((position, error) => {
if (error) return; // 忽略错误情况
// 计算当前位置与围栏中心的距离
const distance = this.calculateDistance(center, position);
const isInside = distance <= radius; // 是否在围栏内
// 触发回调
callback({
position, // 当前位置
distance, // 与围栏中心的距离
isInside, // 是否在围栏内
center, // 围栏中心点
radius // 围栏半径
});
});
// 返回取消监听的函数
return unsubscribe;
}
}
实施策略与性能优势
这些原生Web API相比第三方库具有显著优势:
代码体积缩减:
- Web Components:比React小80-90%
- Fetch API:比Axios小95%
- Web Animations:基础动画比GSAP小70%
性能提升:
- 零依赖包开销
- 直接浏览器级优化
- 更低内存占用
- 更快执行速度
维护优势:
- 无需依赖更新
- 自动随浏览器升级
- 减少安全漏洞
- 更好的长期稳定性
浏览器支持考量:
- 多数API支持率超过90%
- 支持渐进增强
- 旧版浏览器可提供polyfill
利用这些web API, 让你未来的应用实现更小的JavaScript包体积、更优的性能表现和更强的未来适应性。
- 上一篇: 我会在每个项目中复制这10个JS代码片段
- 下一篇: 一个即使是高级前端程序员也不知道的惊人小技巧
猜你喜欢
- 2025-07-07 行业大佬对AI编程的看法:应该用, 但AI仍需大量监督和校对
- 2025-07-07 每个开发者都应该知道的20个Git命令
- 2025-07-07 一键转化github开源仓库为交互式教程神器-Code2Tutorial
- 2025-07-07 7种解决Next.js中累积布局偏移(CLS)的方法
- 2025-07-07 纯前端轻量级的神经网络库brain.js
- 2025-07-07 前端工程师都会喜欢的5个JavaScript库
- 2025-07-07 5个可学习可二次开发的nextjs开源仓库
- 2025-07-07 一个即使是高级前端程序员也不知道的惊人小技巧
- 2025-07-07 我会在每个项目中复制这10个JS代码片段
- 2025-07-07 8个小而美的前端库(前端库,框架大全)
你 发表评论:
欢迎- 07-07使用AI开发招聘网站(100天AI编程实验)
- 07-07Tailwindcss 入门(tailwindcss中文文档)
- 07-07CSS 单位指南(css计量单位)
- 07-07CSS 定位详解(css定位属性的运用)
- 07-07程序员可以作为终身职业吗?什么情况下程序员会开始考虑转行?
- 07-07云和学员有话说:国企转行前端开发,斩获13K高薪!
- 07-0791年转行前端开发,是不是不该转,有啥风险?
- 07-07计算机图形学:变换矩阵(图形学 矩阵变换)
- 594℃几个Oracle空值处理函数 oracle处理null值的函数
- 587℃Oracle分析函数之Lag和Lead()使用
- 575℃0497-如何将Kerberos的CDH6.1从Oracle JDK 1.8迁移至OpenJDK 1.8
- 572℃Oracle数据库的单、多行函数 oracle执行多个sql语句
- 568℃Oracle 12c PDB迁移(一) oracle迁移到oceanbase
- 561℃【数据统计分析】详解Oracle分组函数之CUBE
- 548℃最佳实践 | 提效 47 倍,制造业生产 Oracle 迁移替换
- 541℃Oracle有哪些常见的函数? oracle中常用的函数
- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端react (48)
- 前端aes加密 (58)
- 前端脚手架 (56)
- 前端md5加密 (54)
- 前端路由 (61)
- 前端数组 (73)
- 前端js面试题 (50)
- 前端定时器 (59)
- 前端懒加载 (49)
- 前端获取当前时间 (50)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle 中文 (51)
- oracle的函数 (57)
- 前端调试 (52)
本文暂时没有评论,来添加一个吧(●'◡'●)