You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS下使用WKWebView滚动时如何检测屏幕内可见的HTML元素

实现方案

整体逻辑是通过IntersectionObserver API在网页端检测文本块曝光状态,跨端通信将已读状态同步到原生层,原生层统计完成度后控制下一步操作入口:

1. 预处理静态HTML结构

你在调用loadHTMLString之前,先给需要检测的文本块统一添加标识:

  • 给所有待检测的文本块(比如<p>、<div>标签)增加类名 read-track-block
  • 给每个文本块分配唯一ID,格式如 block-0、block-1,方便统计和去重

如果你不知道原HTML的文本块数量,也可以在JS层动态获取总数后同步给原生层。


2. 注入网页端检测逻辑

创建WKUserScript注入监听逻辑,JS代码示例如下:

// 等待DOM加载完成
document.addEventListener('DOMContentLoaded', function() {
  // 获取所有待检测的文本块
  const trackBlocks = document.querySelectorAll('.read-track-block');
  const total = trackBlocks.length;
  // 先同步总块数给原生层(可选,如果你预处理时已经知道总数可以省略)
  window.webkit.messageHandlers.readBlockHandler.postMessage({
    type: 'total',
    count: total
  });
  const readSet = new Set();
  // 初始化曝光观察器,70%面积进入视口即判定为已读
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      const blockId = entry.target.id;
      if (entry.intersectionRatio >= 0.7 && !readSet.has(blockId)) {
        readSet.add(blockId);
        // 上报已读块ID给原生层
        window.webkit.messageHandlers.readBlockHandler.postMessage({
          type: 'read',
          blockId: blockId
        });
        // 可选:给已读块增加样式提示用户
        entry.target.classList.add('is-read');
      }
    });
  }, { threshold: 0.7 });
  // 给所有文本块绑定观察器
  trackBlocks.forEach(block => {
    observer.observe(block);
  });
});

3. Swift端配置WKWebView

初始化时配置消息监听和脚本注入:

// 1. 初始化配置
let config = WKWebViewConfiguration()
let userContent = WKUserContentController()
// 2. 添加消息监听处理器,名称要和JS端一致
userContent.add(self, name: "readBlockHandler")
// 3. 注入上述JS脚本,页面加载完成后注入
if let jsPath = Bundle.main.path(forResource: "readTrack", ofType: "js"),
   let jsContent = try? String(contentsOfFile: jsPath) {
    let userScript = WKUserScript(source: jsContent, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
    userContent.addUserScript(userScript)
}
config.userContentController = userContent
// 4. 初始化WKWebView
let webView = WKWebView(frame: .zero, configuration: config)
webView.navigationDelegate = self

4. Swift端处理已读统计

实现WKScriptMessageHandler代理方法处理跨端消息:

// 存储总文本块数和已读ID集合
private var totalBlocks: Int = 0
private var readBlockIds: Set<String> = []

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    guard message.name == "readBlockHandler",
          let body = message.body as? [String: Any],
          let type = body["type"] as? String else { return }
    
    switch type {
    case "total":
        // 接收JS上报的总块数
        totalBlocks = body["count"] as? Int ?? 0
    case "read":
        // 接收已读块ID,去重存储
        guard let blockId = body["blockId"] as? String else { return }
        readBlockIds.insert(blockId)
        // 判断是否全部已读
        if readBlockIds.count == totalBlocks && totalBlocks > 0 {
            // 全部已读,解锁下一步按钮
            nextButton.isEnabled = true
        }
    default: break
    }
}

注意事项

  • 避免重复上报:JS层和原生层都做了去重逻辑,不用处理同一块多次触发的问题
  • 长文本适配:如果单块文本高度超过屏幕高度,可以把IntersectionObserver的threshold调整为多个阈值,比如[0.2, 0.5, 0.8],保证用户确实浏览了大部分内容
  • 内存管理:页面销毁时要移除消息处理器,避免内存泄漏:
webView.configuration.userContentController.removeScriptMessageHandler(forName: "readBlockHandler")

内容的提问来源于stack exchange,提问作者ymotov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.17 22:24:04