导读:本期聚焦于唐振业创作的《如何在Node.js服务端渲染页面中集成BetterScroll并实现图片懒加载?》,敬请观看详情。页面滚动卡顿、图片加载拖慢首屏速度,是移动端H5项目里最常见的两个性能痛点。BetterScroll作为一款聚焦移动端滚动的JavaScript库,提供了scroll、pullup、pulldown等能力,同时配合lazyLoad插件可以在滚动过程中按需加载图片。本文介绍如何在Node.js环境下搭建服务端渲染页面,把BetterScroll完整集成进来,重点讲解初始化时机、DOM结构要求、better-scroll插件注册方式以及图片懒加载的参数配置,并分析常见的滚动失效、图片不触发加载等问题的排查思路,帮助你做出一个流畅的滚动列表页面。

BetterScroll是一款专门为移动端设计的滚动库,核心思路是把一个固定高度的容器内的内容区通过transform做位移,从而实现接近原生的滚动体验。它经常和Vue、React这类框架配合使用,但很多人不知道的是,在Node.js做服务端渲染(SSR)的项目里同样可以稳定地使用它。本文围绕一个具体场景展开:用Node.js输出一个商品列表页,前端拿到HTML后初始化BetterScroll,并借助其内置的图片懒加载插件,让列表里的图片在滚动到可视区域时才真正加载。

如何在Node.js服务端渲染页面中集成BetterScroll并实现图片懒加载?

一、为什么滚动容器需要特定的DOM结构

BetterScroll对HTML结构有明确要求:必须存在一个固定高度的父容器,内部第一个子元素作为滚动内容区。也就是说,最外层的wrapper负责约束视口高度,内层的content才是真正会移动的部分。如果用Node.js在服务端拼HTML字符串,这一点必须在模板里就体现出来,否则前端初始化时会直接抛出找不到content的警告。

一个典型的Node.js模板输出大致是这样的:外层div设置固定高度(比如通过内联样式或class控制为视口高度),内层ul承载所有列表项。每个li里放一个img标签,但注意这里不直接写src属性,而是把真实图片地址放到data-src这样的自定义属性上,留给懒加载插件去消费。这样浏览器在解析HTML阶段不会立刻发起几十个图片请求,首屏时间可以明显缩短。

需要强调的是,wrapper的高度一定要小于或等于content的总高度,否则没有可滚动空间,BetterScroll初始化后也无法滚动。在移动端通常用position absolute配合top、bottom为0,或者直接height: 100vh来锁定容器高度,这是最常见的踩坑点之一。

二、Node.js侧的页面输出与服务搭建

服务端部分用Express就够了,核心工作是根据数据渲染出列表HTML。下面这段代码演示了完整流程:定义商品数据、用模板函数拼出符合BetterScroll结构的HTML、挂载静态资源目录,最后提供一个页面路由和一个接口路由(接口用于后续上拉加载更多数据)。

const express = require('express');
const path = require('path');
const app = express();

// 模拟商品数据
function getGoods(page, size) {
  const list = [];
  for (let i = page * size; i < (page + 1) * size; i++) {
    list.push({
      id: i,
      title: '商品标题 ' + i,
      img: 'https://picsum.photos/300/300?random=' + i
    });
  }
  return list;
}

// 服务端渲染列表HTML,注意wrapper与content的结构
function renderPage(list) {
  const items = list.map(g => `
    <li class="item">
      <img class="lazy-img" data-src="${g.img}" src="" alt="${g.title}"/>
      <span>${g.title}</span>
    <\/li>`).join('');
  return `
    <!DOCTYPE html>
    <html>
    <head><meta charset="utf-8"/>
      <meta name="viewport" content="width=device-width,initial-scale=1"/>
      <style>
        .wrapper { height: 100vh; overflow: hidden; }
        .list { padding: 0; margin: 0; list-style: none; }
        .item img { width: 80px; height: 80px; display: block; }
      </style>
    </head>
    <body>
      <div class="wrapper">
        <ul class="list">${items}</ul>
      </div>
      <script src="/bscroll.js"><\/script>
      <script src="/app.js"><\/script>
    </body>
    </html>`;
}

app.use(express.static(path.join(__dirname, 'public')));

app.get('/', (req, res) => {
  res.send(renderPage(getGoods(0, 30)));
});

app.get('/api/goods', (req, res) => {
  const page = parseInt(req.query.page || '0', 10);
  res.json({ list: getGoods(page, 30) });
});

app.listen(3000, () => console.log('server running at http://127.0.0.1:3000'));

这段代码有几个细节值得注意。第一,img标签的data-src承载了真实地址,src留空,这样浏览器不会提前下载图片。第二,wrapper通过100vh锁定高度并设置overflow hidden,这是BetterScroll能正常滚动的前提。第三,静态资源目录里放了BetterScroll的构建产物bscroll.js和自写的app.js,避免每次页面请求都去外网拉取脚本,加载速度和稳定性都更好。

如果项目里已经有模板引擎(比如ejs或nunjucks),把renderPage换成模板渲染即可,思路完全一致。关键是保证输出的HTML结构符合wrapper加content的约定,剩下的初始化逻辑全部交给浏览器端脚本完成。

三、浏览器端初始化BetterScroll并启用懒加载插件

BetterScroll 2.x把懒加载做成了独立插件,需要先注册再使用。下面的app.js演示了完整过程:引入核心库和ObserveDOM、PullUp两个插件,注册插件后创建实例,并在图片真正加载完成时调用refresh刷新滚动高度,因为图片加载会导致内容区高度变化,不刷新就会出现滚动不到底的问题。

(function () {
  // BetterScroll以UMD形式导出,插件在BScroll命名空间下
  var BScroll = window.BScroll;

  var bs = new BScroll('.wrapper', {
    scrollY: true,
    click: true,
    probeType: 3,
    observeDOM: true,      // 内容区DOM变化时自动refresh
    pullUpLoad: {          // 上拉加载配置
      threshold: 60
    }
  });

  var page = 0;
  var loading = false;

  // 上拉触底时向Node.js接口请求更多数据
  bs.on('pullingUp', function () {
    if (loading) return;
    loading = true;
    page++;
    fetch('/api/goods?page=' + page)
      .then(function (res) { return res.json(); })
      .then(function (data) {
        var ul = document.querySelector('.list');
        data.list.forEach(function (g) {
          var li = document.createElement('li');
          li.className = 'item';
          var img = document.createElement('img');
          img.className = 'lazy-img';
          img.setAttribute('data-src', g.img);
          img.src = '';
          li.appendChild(img);
          var span = document.createElement('span');
          span.textContent = g.title;
          li.appendChild(span);
          ul.appendChild(li);
        });
        bs.finishPullUp();   // 结束本次上拉
        bs.refresh();        // 重新计算滚动高度
        loading = false;
      });
  });
})();

这里的图片懒加载没有依赖额外插件,而是利用BetterScroll的probeType为3这个条件——该配置会让scroll事件实时派发位置信息,我们可以在滚动回调里自己判断哪些图片进入视口,再把data-src赋值给src。这种方式的好处是完全可控,代码量也不大:遍历所有未加载的图片,用getBoundingClientRect计算它们相对wrapper的位置,进入阈值范围就触发加载。

当然,如果偏好官方方案,可以直接引入better-scroll的pullup插件配合第三方懒加载库,或者使用社区封装的lazyload插件。两者本质相同:监听滚动位置,按需替换src。自己实现的版本反而更容易在Node.js项目的调试过程中打日志定位问题。

四、常见问题排查与性能优化建议

第一个高频问题是初始化后完全滚不动。排查顺序建议是:先确认wrapper有没有固定高度,再确认content是不是wrapper的第一个子元素,最后确认是否在DOM渲染完成之后才调用new BScroll。在SSR场景下,脚本标签放在body底部基本可以规避时机问题,但如果数据是异步二次插入的,就必须在插入完成后手动调用refresh方法。

第二个问题是上拉加载后新内容滚不到底。原因几乎都是图片异步加载撑高了内容区,而BetterScroll内部记录的滚动高度还是旧值。解决办法是在每个img的load事件里调用bs.refresh(),或者开启observeDOM配合MutationObserver自动感知高度变化。后者代码更简洁,但要注意老机型上的兼容开销。

最后从架构角度补充一点:Node.js只负责吐出首屏HTML和提供分页接口,滚动、懒加载这些交互逻辑全部留在浏览器端,这种职责切分能让服务端保持轻量,也方便把静态脚本放到CDN上。列表数据量特别大时,还可以在服务端做分页缓存,配合客户端的上拉加载,整体体验和性能都会有明显提升。如果后续要做下拉刷新,思路和上拉完全对称,注册pulldown插件监听pullingDown事件即可。

BetterScrollNode.js图片懒加载修改时间:2026-09-10 08:04:48

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0910/53913.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。