找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 146|回复: 0

服务端流式渲染 iOS 中踩坑记

[复制链接]

1091

主题

0

回帖

3307

积分

管理员

积分
3307
发表于 2023-12-3 13:53:28 | 显示全部楼层 |阅读模式

原文链接(查看演示视频更友好): https://github.com/xiaoxiaojx/blog/issues/37

近期 IOS 客户端反映 WebView 中打开 h5 页面存在明显的白屏时间, 于是打算把后端接口延时高(> 150ms )的 h5 项目由现在的 SSR 改成 html 请求达到 Node 时率先返回构建时生成的骨架屏 html 主体, 然后再异步请求后端接口数据, 获取到接口数据后再追加到 html 响应流中。这样 Node 能够 1ms 内响应实际内容让用户先看到页面框架, 通过内网并发聚合的接口数据也能让客户端直接复用这部分数据更快展示出最终屏。
按理来说 h5 不再受限于后端接口的响应时长, 能够第一时间渲染出骨架屏页面, 但是体验后白屏时间好像没怎么缩短? 最后反复删减代码测试发现了一个残酷的现实 👇
IOS WKWebView 不支持流式渲染(分块渲染), 安卓 WebView 与 PC Chrome 是支持的。
即表示 IOS 中会等待 html 请求彻底结束后才开始渲染, 如下是安卓与 IOS 中的效果演示视频,希望其他同学不要再踩坑 🤯
https://user-images.githubusercontent.com/23253540/174447134-25daa11b-0be8-4330-85b7-e464c14f6047.mp4
https://user-images.githubusercontent.com/23253540/174447157-8ccc2be4-52fe-4d67-a11d-d4701677aa5d.mp4

2022-06-20 更新,经过大佬提醒,IOS 中如果返回的 data 是普通文本文字,或返回的数据中包含普通文本文字,那只需要达到非空 200 字节即可以触发渲染,详细见 iOS 之深入解析 WKWebView 加载的生命周期与代理方法
https://user-images.githubusercontent.com/23253540/174550696-cb3b54df-6db1-4aff-8adb-b60258461b20.mp4
所以 IOS chrome 与 safari 也是支持流式渲染(分块渲染),App 中没有效果是有效内容没有达到 200 字节 (innerText)
h5 页面首屏文字等内容达到 200+ 字节还是较少的,设置为 display: none 来凑数的 div 不会被计数进去,相关代码实现见
// https://github.com/WebKit/webkit/blob/main/Source/WebCore/page/FrameView.h#L975

static const unsigned visualCharacterThreshold = 200;

// https://github.com/WebKit/WebKit/blob/ed7fed17c5ac886890859f1fc8682dba06424616/Source/WebCore/page/FrameView.cpp#L4685

void FrameView::checkAndDispatchDidReachVisuallyNonEmptyState()
{
// ...
// The first few hundred characters rarely contain the interesting content of the page.
        if (m_visuallyNonEmptyCharacterCount > visualCharacterThreshold)
            return true;
}

void FrameView::incrementVisuallyNonEmptyCharacterCount(const String& inlineText)
{
    if (m_visuallyNonEmptyCharacterCount > visualCharacterThreshold && m_hasReachedSignificantRenderedTextThreshold)
        return;

    auto nonWhitespaceLength = [](auto& inlineText) {
        auto length = inlineText.length();
        for (unsigned i = 0; i < inlineText.length(); ++i) {
            if (isNotHTMLSpace(inlineText[i]))
                continue;
            --length;
        }
        return length;
    };
    m_visuallyNonEmptyCharacterCount += nonWhitespaceLength(inlineText);
    ++m_textRendererCountForVisuallyNonEmptyCharacters;
}


2022-06-26 更新,最后给 body 标签插入了一个塞了 200 个空格字符的 div 来强制 WKWebView 进行刷新缓存实时渲染,经过一周多的测试,白屏时间明显减少甚至不见!
const IOS_200 = `<div style="height:0;width:0;">\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b</div>`

把服务端渲染 SSR 改为现在的预渲染+接口聚合还有什么其他优势吗?

新的预渲染+接口聚合架构在公司 CDN 基建支持的情况下,不就是天然的 ⚡️ ESR (边缘流式渲染方案) 么 ~


强👍
牛逼,mark 一下,万一用得上
@DingJZ iOS 哪天明显比安卓慢,就能用上了
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|张迁碑

GMT+8, 2024-10-31 19:16 , Processed in 0.100359 second(s), 24 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表