pin_drop当前位置:知识文库 ❯ 图文

HTML5 API:HTML5 Notification API - 完整教程与代码示例

一、教程简介

Notification API 允许 Web 应用在浏览器之外向用户显示桌面通知,即使页面不在当前标签页或浏览器被最小化,通知也能正常显示。该 API 广泛应用于消息提醒、邮件通知、任务完成提示、系统告警等场景,是构建实时通知系统的核心接口。


二、核心概念

通知生命周期

代码示例

请求权限 → 创建通知 → 显示通知 → 用户交互 → 通知关闭
    │           │          │          │          │
    │           │          │     onclick      auto/手动
    │           │          │     onclose
    │           │      onshow
    │       new Notification()
    Notification.requestPermission()

权限状态

状态 说明
default 未询问,等同于拒绝
granted 用户已授权
denied 用户已拒绝

Notification 选项

代码示例

new Notification(title, {
    dir: 'auto',              // 文字方向:auto/ltr/rtl
    lang: 'zh-CN',            // 语言
    body: '通知正文内容',      // 正文
    tag: 'unique-tag',        // 标签(同标签通知会替换)
    icon: '/icon.png',        // 图标 URL
    image: '/preview.jpg',    // 大图(部分浏览器支持)
    badge: '/badge.png',      // 徽章图标
    vibrate: [200, 100, 200], // 振动模式(移动端)
    timestamp: Date.now(),    // 时间戳
    renotify: false,          // 相同 tag 是否重新通知
    silent: false,            // 是否静音
    requireInteraction: false, // 是否需要用户手动关闭
    actions: [                // 操作按钮(Service Worker 通知)
        { action: 'reply', title: '回复' },
        { action: 'ignore', title: '忽略' }
    ]
});

三、语法与用法

请求权限

代码示例

// 现代写法(返回 Promise)
Notification.requestPermission().then(permission => {
    if (permission === 'granted') {
        console.log('通知权限已授予');
    }
});

// 或使用 async/await
async function requestPermission() {
    const permission = await Notification.requestPermission();
    return permission === 'granted';
}

创建通知

代码示例

if (Notification.permission === 'granted') {
    const notification = new Notification('新消息', {
        body: '你收到了一条新消息',
        icon: '/icon.png',
        tag: 'message-1'
    });
}

事件处理

代码示例

const notification = new Notification('点击通知', {
    body: '点击此通知打开页面'
});

notification.onshow = function() {
    console.log('通知已显示');
};

notification.onclick = function() {
    window.focus(); // 聚焦窗口
    window.open('https://example.com', '_blank');
    notification.close();
};

notification.onclose = function() {
    console.log('通知已关闭');
};

notification.onerror = function() {
    console.error('通知错误');
};

通知标签(tag)

代码示例

// 相同 tag 的通知会替换旧通知,避免通知堆积
new Notification('聊天消息', {
    body: '张三: 你好!',
    tag: 'chat-zhangsan'
});

// 新消息会替换上面的通知
new Notification('聊天消息', {
    body: '张三: 在吗?',
    tag: 'chat-zhangsan'
});

四、代码示例

示例一:通知管理面板

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Notification API - 通知管理</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background: #f0f2f5;
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container { max-width: 640px; margin: 0 auto; }
        h1 { font-size: 24px; color: #1a1a2e; margin-bottom: 8px; }
        .desc { color: #666; font-size: 14px; margin-bottom: 24px; }
        .card {
            background: #fff;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 16px;
            box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
        }
        .card h2 { font-size: 16px; color: #333; margin-bottom: 16px; }
        .permission-bar {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 16px;
            border-radius: 10px;
            margin-bottom: 16px;
        }
        .permission-bar.granted { background: #e6f4ea; }
        .permission-bar.denied { background: #fce8e6; }
        .permission-bar.default { background: #fff3cd; }
        .permission-status { font-size: 14px; font-weight: 600; }
        .permission-bar.granted .permission-status { color: #1e7e34; }
        .permission-bar.denied .permission-status { color: #c62828; }
        .permission-bar.default .permission-status { color: #856404; }
        button {
            padding: 8px 18px;
            border: none;
            border-radius: 8px;
            font-size: 13px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
        }
        .btn-primary { background: #0984e3; color: #fff; }
        .btn-primary:hover { background: #0770c2; }
        .btn-success { background: #00b894; color: #fff; }
        .btn-success:hover { background: #00a381; }
        .btn-warning { background: #fdcb6e; color: #333; }
        .btn-warning:hover { background: #e5b85c; }
        .btn-danger { background: #e17055; color: #fff; }
        .btn-danger:hover { background: #d35f48; }
        .btn-group { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
        .form-group { margin-bottom: 14px; }
        .form-group label {
            display: block;
            font-size: 13px;
            color: #666;
            margin-bottom: 6px;
            font-weight: 500;
        }
        input, select, textarea {
            width: 100%;
            padding: 8px 12px;
            border: 1px solid #ddd;
            border-radius: 8px;
            font-size: 14px;
            outline: none;
        }
        input:focus, select:focus, textarea:focus { border-color: #0984e3; }
        textarea { resize: vertical; min-height: 60px; }
        .notification-log {
            max-height: 300px;
            overflow-y: auto;
        }
        .log-item {
            display: flex;
            align-items: center;
            padding: 10px 0;
            border-bottom: 1px solid #f0f0f0;
            font-size: 13px;
        }
        .log-item:last-child { border-bottom: none; }
        .log-icon {
            width: 32px;
            height: 32px;
            border-radius: 8px;
            display: flex;
            align-items: center;
            justify-content: center;
            margin-right: 12px;
            font-size: 14px;
            flex-shrink: 0;
        }
        .log-icon.info { background: #e8f0fe; color: #4285f4; }
        .log-icon.success { background: #e6f4ea; color: #34a853; }
        .log-icon.warning { background: #fef7e0; color: #f9ab00; }
        .log-icon.error { background: #fce8e6; color: #ea4335; }
        .log-info { flex: 1; }
        .log-title { font-weight: 600; color: #333; }
        .log-body { color: #666; font-size: 12px; margin-top: 2px; }
        .log-time { color: #999; font-size: 11px; }
        .quick-tpl {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 8px;
        }
        .tpl-btn {
            padding: 12px;
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            background: #fff;
            cursor: pointer;
            transition: all 0.2s;
            text-align: left;
        }
        .tpl-btn:hover { border-color: #0984e3; background: #f0f7ff; }
        .tpl-title { font-size: 13px; font-weight: 600; color: #333; }
        .tpl-desc { font-size: 11px; color: #999; margin-top: 4px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>浏览器通知管理</h1>
        <p class="desc">体验 Notification API,发送自定义桌面通知。</p>

        <div class="permission-bar default" id="permBar">
            <span class="permission-status" id="permStatus">通知权限:未请求</span>
            <button class="btn-primary" id="permBtn" onclick="requestPermission()">请求权限</button>
        </div>

        <div class="card">
            <h2>快速通知模板</h2>
            <div class="quick-tpl">
                <div class="tpl-btn" onclick="sendTemplate('message')">
                    <div class="tpl-title">新消息</div>
                    <div class="tpl-desc">模拟收到新消息通知</div>
                </div>
                <div class="tpl-btn" onclick="sendTemplate('success')">
                    <div class="tpl-title">操作成功</div>
                    <div class="tpl-desc">任务完成通知</div>
                </div>
                <div class="tpl-btn" onclick="sendTemplate('warning')">
                    <div class="tpl-title">系统警告</div>
                    <div class="tpl-desc">资源告警通知</div>
                </div>
                <div class="tpl-btn" onclick="sendTemplate('reminder')">
                    <div class="tpl-title">日程提醒</div>
                    <div class="tpl-desc">定时提醒通知</div>
                </div>
            </div>
        </div>

        <div class="card">
            <h2>自定义通知</h2>
            <div class="form-group">
                <label>通知标题</label>
                <input type="text" id="notifTitle" value="自定义通知" placeholder="输入标题">
            </div>
            <div class="form-group">
                <label>通知内容</label>
                <textarea id="notifBody" placeholder="输入通知内容">这是一条自定义的浏览器通知</textarea>
            </div>
            <div class="form-group">
                <label>通知标签(同标签会替换)</label>
                <input type="text" id="notifTag" placeholder="可选,留空则不设置">
            </div>
            <div class="btn-group">
                <button class="btn-success" onclick="sendCustom()">发送通知</button>
                <button class="btn-warning" onclick="sendWithDelay()">5秒后发送</button>
            </div>
        </div>

        <div class="card">
            <h2>通知历史</h2>
            <div class="notification-log" id="notifLog">
                <div style="text-align:center;color:#999;padding:20px;font-size:13px;">暂无通知记录</div>
            </div>
        </div>
    </div>

    <script>
        const templates = {
            message: { title: '新消息', body: '张三向你发送了一条消息:项目进展如何?', icon: '', tag: 'chat-message' },
            success: { title: '操作成功', body: '文件已成功上传至服务器,共处理 156 个文件。', icon: '', tag: 'task-complete' },
            warning: { title: '系统警告', body: 'CPU 使用率已达到 92%,请检查后台进程。', icon: '', tag: 'system-warning' },
            reminder: { title: '日程提醒', body: '15:00 产品评审会议即将开始,请准时参加。', icon: '', tag: 'calendar-reminder' }
        };

        let notifCount = 0;

        function updatePermissionUI() {
            const permission = Notification.permission;
            const bar = document.getElementById('permBar');
            const status = document.getElementById('permStatus');
            const btn = document.getElementById('permBtn');
            bar.className = 'permission-bar ' + permission;
            const labels = { granted: '已授权', denied: '已拒绝', default: '未请求' };
            status.textContent = '通知权限:' + labels[permission];
            if (permission === 'granted') { btn.textContent = '已授权'; btn.disabled = true; btn.style.opacity = '0.5'; }
            else if (permission === 'denied') { btn.textContent = '已拒绝'; btn.disabled = true; btn.style.opacity = '0.5'; }
            else { btn.textContent = '请求权限'; btn.disabled = false; btn.style.opacity = '1'; }
        }

        async function requestPermission() {
            const permission = await Notification.requestPermission();
            updatePermissionUI();
            if (permission !== 'granted') { alert('通知权限被拒绝,请在浏览器设置中手动开启。'); }
        }

        function sendNotification(title, options) {
            if (!('Notification' in window)) { alert('您的浏览器不支持 Notification API'); return; }
            if (Notification.permission !== 'granted') { alert('请先授予通知权限'); return; }
            try {
                const notification = new Notification(title, options);
                notification.onclick = function () { window.focus(); notification.close(); };
                addLog(title, options.body || '', options.tag || '');
                notifCount++;
                setTimeout(() => notification.close(), 5000);
            } catch (e) { console.error('通知发送失败:', e); }
        }

        function sendTemplate(type) { const tpl = templates[type]; sendNotification(tpl.title, { body: tpl.body, tag: tpl.tag }); }

        function sendCustom() {
            const title = document.getElementById('notifTitle').value.trim();
            const body = document.getElementById('notifBody').value.trim();
            const tag = document.getElementById('notifTag').value.trim();
            if (!title) { alert('请输入通知标题'); return; }
            const options = { body: body };
            if (tag) options.tag = tag;
            sendNotification(title, options);
        }

        function sendWithDelay() {
            const title = document.getElementById('notifTitle').value.trim() || '延迟通知';
            const body = document.getElementById('notifBody').value.trim() || '这是一条延迟5秒发送的通知';
            const tag = document.getElementById('notifTag').value.trim();
            setTimeout(function () {
                const options = { body: body };
                if (tag) options.tag = tag;
                sendNotification(title, options);
            }, 5000);
            alert('通知将在5秒后发送,请切换到其他标签页或最小化窗口查看效果');
        }

        function addLog(title, body, tag) {
            const log = document.getElementById('notifLog');
            if (notifCount === 0) log.innerHTML = '';
            const time = new Date().toLocaleTimeString('zh-CN');
            const item = document.createElement('div');
            item.className = 'log-item';
            item.innerHTML = '<div class="log-icon info">&#128276;</div><div class="log-info"><div class="log-title">' + escapeHtml(title) + '</div><div class="log-body">' + escapeHtml(body) + (tag ? ' (tag: ' + escapeHtml(tag) + ')' : '') + '</div></div><span class="log-time">' + time + '</span>';
            log.insertBefore(item, log.firstChild);
        }

        function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; }
        updatePermissionUI();
    </script>
</body>
</html>

五、浏览器兼容性

浏览器 支持版本 备注
Chrome 22+ 完整支持
Firefox 22+ 完整支持
Safari 6+ 部分功能限制
Edge 14+ 完整支持
Opera 25+ 完整支持
IE 不支持 -
iOS Safari 16.4+ 需添加到主屏幕
Android 4.4+ 完整支持

六、注意事项与最佳实践

1. 权限请求时机

代码示例

// 不推荐:页面加载时立即请求
window.addEventListener('load', () => Notification.requestPermission());

// 推荐:用户操作后请求
button.addEventListener('click', () => {
    if (Notification.permission === 'default') {
        Notification.requestPermission();
    }
});

2. 通知频率控制

代码示例

// 避免短时间内发送大量通知
class NotificationThrottler {
    constructor(minInterval = 5000) {
        this.minInterval = minInterval;
        this.lastSent = 0;
        this.queue = [];
    }

    send(title, options) {
        const now = Date.now();
        if (now - this.lastSent >= this.minInterval) {
            new Notification(title, options);
            this.lastSent = now;
        } else {
            this.queue.push({ title, options });
        }
    }
}

3. 使用 tag 避免通知堆积

代码示例

// 同一对话的消息使用相同 tag,新消息替换旧通知
new Notification('聊天消息', {
    body: '最新消息内容',
    tag: 'chat-user-123'
});

4. 降级方案

代码示例

function showNotification(title, options) {
    if (!('Notification' in window)) {
        // 降级:页面内通知
        showInPageNotification(title, options.body);
        return;
    }

    if (Notification.permission === 'granted') {
        new Notification(title, options);
    } else if (Notification.permission !== 'denied') {
        Notification.requestPermission().then(permission => {
            if (permission === 'granted') {
                new Notification(title, options);
            }
        });
    }
}

七、代码规范示例

代码示例

// 推荐:封装通知管理类
class NotificationManager {
    constructor() {
        this.supported = 'Notification' in window;
        this.throttleMap = new Map();
    }

    get permission() {
        return this.supported ? Notification.permission : 'denied';
    }

    async requestPermission() {
        if (!this.supported) return false;
        if (this.permission === 'granted') return true;
        if (this.permission === 'denied') return false;

        const result = await Notification.requestPermission();
        return result === 'granted';
    }

    async send(title, options = {}) {
        if (!this.supported) {
            console.warn('Notification API 不可用');
            return null;
        }

        if (this.permission !== 'granted') {
            const granted = await this.requestPermission();
            if (!granted) return null;
        }

        try {
            const notification = new Notification(title, {
                icon: options.icon || '/favicon.ico',
                badge: options.badge,
                body: options.body || '',
                tag: options.tag,
                requireInteraction: options.requireInteraction || false,
                silent: options.silent || false,
                ...options
            });

            if (options.onclick) notification.onclick = options.onclick;
            if (options.onclose) notification.onclose = options.onclose;

            if (options.autoClose !== false) {
                setTimeout(() => notification.close(), options.closeTimeout || 5000);
            }

            return notification;
        } catch (e) {
            console.error('通知发送失败:', e);
            return null;
        }
    }

    // 节流发送
    throttledSend(key, title, options, minInterval = 5000) {
        const now = Date.now();
        const lastSent = this.throttleMap.get(key) || 0;

        if (now - lastSent < minInterval) return null;
        this.throttleMap.set(key, now);
        return this.send(title, options);
    }
}

// 使用示例
const notify = new NotificationManager();

async function init() {
    const granted = await notify.requestPermission();
    if (granted) {
        notify.send('欢迎回来', { body: '你有3条未读消息', tag: 'welcome' });
    }
}

八、常见问题与解决方案

常见问题

通知权限被拒绝后如何重新获取?

一旦用户拒绝,无法通过代码再次请求。只能引导用户手动在浏览器设置中修改。

iOS Safari 不显示通知?

iOS 16.4+ 支持通知,但需要将 Web App 添加到主屏幕,且使用 Service Worker 注册通知。

通知的图标不显示?

图标 URL 必须是绝对路径,且需要同源或支持 CORS。解决方案:使用完整的 HTTPS URL 作为图标路径。

如何实现定时通知?

使用 setTimeout 延迟创建通知即可实现定时通知功能。例如:setTimeout(() => new Notification(title, options), 5 * 60 * 1000) 可以在5分钟后发送通知。


九、总结

Notification API 提供了在浏览器之外向用户显示桌面通知的能力,适用于消息提醒、任务完成通知、系统告警等场景。使用时需注意权限管理(在用户操作后请求)、通知频率控制(使用 tag 避免堆积)、降级方案(不支持时使用页面内通知)和自动关闭机制。通过封装通知管理类可以实现权限检查、节流发送、自动关闭等高级功能,使通知系统更加健壮和用户友好。

标签: 浏览器通知 Notification API 桌面通知 权限管理 消息提醒 HTML5 API

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: HTML5 API:HTML5 FileReader API - 完整教程与代码示例 下一篇: HTML5 API:HTML5 Clipboard API - 完整教程与代码示例

poll相关推荐