构建离线友好的图片上传系统 - 使用PWA技术实现可靠上传

本文详细介绍了如何利用PWA技术(IndexedDB、Service Worker和Background Sync API)构建离线友好的图片上传系统,解决网络不稳定时的上传难题,提升用户体验。

构建离线友好的图片上传系统

网络连接不稳定不意味着用户体验必须糟糕。借助PWA技术如IndexedDB、Service Worker和Background Sync API,你可以构建一个离线友好的图片上传系统,它能排队上传并在网络恢复时自动重试 - 让用户即使离线也能无忧上传。

问题背景

当你填写在线表单需要上传文件时,网络突然中断,文件消失,不得不重新上传。糟糕的网络连接会让你花费大量时间反复尝试上传。这种需要不断检查网络状态并多次重试的体验非常糟糕。

解决方案概述

我们可以通过调整图片上传系统,让用户能够离线上传图片 - 消除对可靠网络连接的依赖,并在网络恢复时自动重试上传过程,无需用户干预。

系统流程图

  1. 用户选择图片
  2. 图片存储在本地IndexedDB中
  3. Service Worker检测网络恢复
  4. 后台同步处理待上传内容
  5. 文件成功上传后删除本地副本

系统实现

选择图片方式

提供两种选择方式:

  • 简单的<input type="file">元素
  • 拖放界面 建议同时提供两种方式以提升用户体验,还可以考虑使用Clipboard API允许用户直接粘贴图片。

注册Service Worker

1
2
3
4
5
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js')
    .then(reg => console.log('Service Worker registered', reg))
    .catch(err => console.error('Service Worker registration failed', err));
}

检查网络连接

1
2
3
4
5
6
7
8
function uploadImage() {
  if (navigator.onLine) {
    // 直接上传图片
  } else {
    // 注册同步事件
    // 将图片存储在IndexedDB中
  }
}

注意:navigator.onLine属性并不可靠,建议实现自定义的网络检测方案。

注册同步事件

1
2
3
4
5
6
7
async function registerSyncEvent() {
  if ('SyncManager' in window) {
    const registration = await navigator.serviceWorker.ready;
    await registration.sync.register('uploadImages');
    console.log('Background Sync registered');
  }
}

Service Worker监听同步事件

1
2
3
4
5
self.addEventListener('sync', (event) => {
  if (event.tag === 'uploadImages') {
    event.waitUntil(sendImages());
  }
});

打开IndexedDB数据库

 1
 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
let database; // 全局变量存储数据库实例

function openDatabase() {
  return new Promise((resolve, reject) => {
    if (database) return resolve(database);

    const request = indexedDB.open("myDatabase", 1);

    request.onerror = (event) => {
      console.error("Database error:", event.target.error);
      reject(event.target.error);
    };

    request.onupgradeneeded = (event) => {
        const db = event.target.result;
        if (!db.objectStoreNames.contains("images")) {
          db.createObjectStore("images", { keyPath: "id" });
        }
        console.log("Database setup complete.");
    };

    request.onsuccess = (event) => {
      database = event.target.result;
      resolve(database);
    };
  });
}

在IndexedDB中存储图片

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
async function storeImages(file) {
  const db = await openDatabase();
  const transaction = db.transaction("images", "readwrite");
  const store = transaction.objectStore("images");
  const imageRecord = {
    id: IMAGE_ID,   // 唯一ID
    image: file     // 存储图片文件(Blob)
  };
  const addRequest = store.add(imageRecord);
  addRequest.onsuccess = () => console.log("图片添加成功!");
  addRequest.onerror = (e) => console.error("存储图片错误:", e.target.error);
}

检索并上传图片

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
async function retrieveAndUploadImage(IMAGE_ID) {
  try {
    const db = await openDatabase();
    const transaction = db.transaction("images", "readonly");
    const store = transaction.objectStore("images");
    const request = store.get(IMAGE_ID);
    request.onsuccess = function (event) {
      const image = event.target.result;
      if (image) {
        // 在此上传图片到服务器
      } else {
        console.log("未找到ID对应的图片:", IMAGE_ID);
      }
    };
    request.onerror = () => {
        console.error("检索图片错误。");
    };
  } catch (error) {
    console.error("打开数据库失败:", error);
  }
}

删除IndexedDB数据库

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function deleteDatabase() {
  if (database) {
    database.close();
    console.log("数据库连接已关闭。");
  }

  const deleteRequest = indexedDB.deleteDatabase("myDatabase");

  deleteRequest.onsuccess = function () {
    console.log("数据库删除成功!");
  };

  deleteRequest.onerror = function (event) {
    console.error("删除数据库错误:", event.target.error);
  };

  deleteRequest.onblocked = function () {
    console.warn("数据库删除被阻止。请关闭所有连接后重试。");
  };
}

限制与注意事项

  1. 没有可靠的网络连接检测:JavaScript没有提供万无一失的在线状态检测方法。
  2. 仅限Chromium解决方案:Background Sync API目前仅限于基于Chromium的浏览器。
  3. IndexedDB存储策略:浏览器对IndexedDB有存储限制和回收策略。

提升用户体验

由于整个过程在后台进行,我们需要通过UI元素告知用户图片的状态:

  • Toast通知
  • 上传状态指示器(如旋转图标)
  • 进度条
  • 网络状态指示器
  • 重试和取消按钮

总结

通过利用PWA技术如IndexedDB、Service Worker和Background Sync API,开发者可以提升Web应用在不可靠网络环境下的可靠性,特别为网络连接不稳定的地区用户提供更好的体验。

(评论部分略)

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计