前端首屏优化

首屏优化是指在网页加载过程中,尽快展示给用户可见的内容,以提高用户体验和页面加载速度。关键性能指标是FCP(First Contentful Paint,首次内容绘制),还有LCP、TTI。

  • FCP(首次内容绘制):浏览器在屏幕上第一次绘制出“有内容”的东西的时间点。这个“内容”可以是文本、图片(包括背景图)、<svg> 元素,或者是非白色的 <canvas> 元素。简单来说,就是从用户输入网址按下回车,到页面上出现第一个可见元素的耗时。

    如何测量 FCP?

    1. 使用 Web Vitals

      Web Vitals 是 Google 提供的一个库,它专注于网站核心的 Web 性能指标,包括 FCP。

      安装 Web Vitals:

      你可以将 web-vitals 库集成到你的前端项目中,使用 JavaScript API 来捕获 FCP 等指标。

      npm install web-vitals
      
      代码示例:
      import { getFCP } from 'web-vitals';
      
      getFCP(console.log);
      
    2. Performance API

      现代浏览器提供了 PerformanceObserver API,让我们可以轻松地在代码中监控 FCP。

      // 创建一个 PerformanceObserver 实例来观察 'paint' 类型的性能条目
      const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          // 我们只关心 'first-contentful-paint'
          if (entry.name === 'first-contentful-paint') {
            console.log(`FCP time: ${entry.startTime}ms`);
          }
        }
      });
      
      // 开始观察,buffered: true 表示我们也可以获取在观察开始前就已经发生的 paint 事件
      observer.observe({ type: 'paint', buffered: true });
      
    3. 第三方插件
      • WebPageTest:一个非常强大的在线工具,可以用来测试 FCP、LCP、FMP 等关键性能指标,并生成详细报告。
      • SpeedCurve:一个综合的性能监控平台,可以监控网站的 FCP、LCP、FMP 等指标,适合长期跟踪和比较。
      • New Relic:通过它,你可以监控 FCP 和其他 Web 性能指标,适合企业级应用。
  • LCP (Largest Contentful Paint,最大内容绘制):视口内最大的图片或文本块完成渲染的时间。LCP 告诉我们页面的主要内容大概什么时候对用户可见。一个好的 LCP 时间应该在 2.5 秒以内。

  • TTI (Time to Interactive,可交互时间):这个指标衡量的是页面何时完全具备交互能力。也就是说,不仅内容都显示出来了,而且主线程也已经空闲,可以随时响应用户的点击、滚动等操作了。

常用方法

  1. 代码分割 (Code Splitting)

    现代前端框架和打包工具都原生支持代码分割。核心思想就是:按需加载

    实战:使用动态 import()

    ESM(ES模块)规范中的动态 import() 语法是实现代码分割的利器。它返回一个 Promise,让我们可以异步地加载模块。

    在 Vue 中,你可以使用异步组件;在 React 中,你可以使用 React.lazySuspense,它们的底层原理都离不开动态导入。

  2. 压缩和合并资源

    通过压缩CSS、JavaScript和HTML等静态资源文件,并将它们合并为较少的文件,可以减少网络请求次数和文件大小,加快页面加载速度。

    • 压缩

      以下是一个示例Webpack配置文件,展示如何使用css-minimizer-webpack-pluginmini-css-extract-plugin压缩CSS文件和terser-webpack-plugin压缩JavaScript文件。

      const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
      const TerserPlugin = require('terser-webpack-plugin');
      const MiniCssExtractPlugin = require('mini-css-extract-plugin');
      
      module.exports = {
        // 入口文件
        entry: './src/index.js',
        // 输出文件
        output: {
          filename: 'bundle.min.js',
          path: __dirname + '/dist',
        },
        // 模块加载器
        module: {
          rules: [
            // 处理CSS文件
            {
              test: /\.css$/i,
              use: [MiniCssExtractPlugin.loader, 'css-loader'],
            },
            // 处理JavaScript文件
            {
              test: /\.js$/i,
              exclude: /node_modules/,
              use: {
                loader: 'babel-loader',
                options: {
                  presets: ['@babel/preset-env', '@babel/preset-react'],
                },
              },
            },
          ],
        },
        // 插件
        plugins: [
          new MiniCssExtractPlugin({
            filename: 'styles.min.css',
          }),
        ],
        // 优化配置
        optimization: {
          minimizer: [
            // 压缩CSS文件
            new CssMinimizerPlugin(),
            // 压缩JavaScript文件
            new TerserPlugin(),
          ],
        },
      };
      
    • 合并静态资源

      index.html文件中,我们将引入压缩和合并后的静态资源文件。

      <!DOCTYPE html>
      <html>
      <head>
        <meta charset="UTF-8">
        <title>React App</title>
        <link rel="stylesheet" href="styles.min.css">
      </head>
      <body>
        <div id="root"></div>
      
        <script src="bundle.min.js"></script>
      </body>
      </html>
      

      通过上述代码,我们将压缩和合并后的CSS文件和JavaScript文件引入到了index.html中。这样做可以减少网络请求次数和文件大小,提高页面加载速度。

  3. 图片优化

    使用适当的图片格式(如JPEG、PNG、WebP)和压缩算法,对图片进行优化,减小图片文件大小,提高加载速度。同时,可以使用懒加载技术,延迟加载非首屏可见区域的图片,减少首屏的加载时间。

    举例一个懒加载示例

    <!DOCTYPE html>
    <html>
    <head>
      <style>
        .image-container {
          height: 200px;
          width: 200px;
          overflow: hidden;
          background-color: #eee;
        }
    
        img {
          display: block;
          height: auto;
          width: 100%;
        }
      </style>
    </head>
    <body>
      <div class="image-container">
        <img data-src="image.jpg" alt="Image" />
      </div>
    </body>
    <script>
      document.addEventListener("DOMContentLoaded", function() {
      var lazyImages = [].slice.call(document.querySelectorAll("img[data-src]"));
    
      if ("IntersectionObserver" in window) {
        var lazyImageObserver = new IntersectionObserver(function(entries, observer) {
          entries.forEach(function(entry) {
            if (entry.isIntersecting) {
              var lazyImage = entry.target;
              lazyImage.src = lazyImage.dataset.src;
              lazyImage.removeAttribute("data-src");
              lazyImageObserver.unobserve(lazyImage);
            }
          });
        });
    
        lazyImages.forEach(function(lazyImage) {
          lazyImageObserver.observe(lazyImage);
        });
      } else {
        // Fallback for browsers that don't support IntersectionObserver
        var lazyLoad = function() {
          lazyImages.forEach(function(lazyImage) {
            if (lazyImage.getBoundingClientRect().top <= window.innerHeight && lazyImage.getBoundingClientRect().bottom >= 0 && getComputedStyle(lazyImage).display !== "none") {
              lazyImage.src = lazyImage.dataset.src;
              lazyImage.removeAttribute("data-src");
              lazyImages = lazyImages.filter(function(image) {
                return image !== lazyImage;
              });
    
              if (lazyImages.length === 0) {
                document.removeEventListener("scroll", lazyLoad);
                window.removeEventListener("resize", lazyLoad);
                window.removeEventListener("orientationchange", lazyLoad);
              }
            }
          });
        };
    
        document.addEventListener("scroll", lazyLoad);
        window.addEventListener("resize", lazyLoad);
        window.addEventListener("orientationchange", lazyLoad);
      }
    });
    
    </script>
    </html>
    

    示例代码使用 IntersectionObserver API 实现图片懒加载,如果浏览器不支持该 API,则会回退到基于滚动事件的实现方式。

    具体来说,该示例首先获取所有带有 data-src 属性的图片元素,并将它们保存在一个数组中。然后,如果浏览器支持 IntersectionObserver API,则创建一个 IntersectionObserver 对象,并为每个图片元素添加观察器。当图片元素进入视口时,观察器会将其 data-src 属性值设置为 src 属性值,并移除 data-src 属性。最后,观察器会取消对该图片元素的观察。

    如果浏览器不支持 IntersectionObserver API,则会使用一个 lazyLoad 函数,该函数会在滚动、窗口大小调整或设备方向变化时被触发。在该函数中,会遍历所有带有 data-src 属性的图片元素,并检查它们是否在视口中可见。如果是,则将其 data-src 属性值设置为 src 属性值,并移除 data-src 属性。最后,会从数组中删除已经加载的图片元素,并检查数组是否为空。如果为空,则取消事件监听。

  4. 异步加载

    将不影响首屏展示的资源(如统计代码、广告等)使用异步加载方式引入,避免阻塞首屏内容的加载。

    可以使用JavaScript的动态脚本加载技术。通过动态创建<script>标签,并将其插入到文档中,可以实现异步加载资源而不阻塞首屏内容的加载。

    举个示例:

    function loadExternalScript(url) {
      return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = url;
        script.async = true;
    
        script.onload = () => {
          resolve();
        };
    
        script.onerror = () => {
          reject(new Error(`Failed to load script ${url}`));
        };
    
        document.head.appendChild(script);
      });
    }
    
    // 异步加载统计代码
    loadExternalScript('https://example.com/analytics.js')
      .then(() => {
        // 统计代码加载完成后执行的逻辑
        console.log('Analytics script loaded');
      })
      .catch((error) => {
        console.error(error);
      });
    
    // 异步加载广告代码
    loadExternalScript('https://example.com/advertisement.js')
      .then(() => {
        // 广告代码加载完成后执行的逻辑
        console.log('Advertisement script loaded');
      })
      .catch((error) => {
        console.error(error);
      });
    

    在上面的代码中,loadExternalScript()函数用于动态加载外部脚本。通过将async属性设置为true,确保脚本以异步方式加载,不会阻塞其他资源的加载。然后,我们可以在then()方法中执行相应的逻辑,以响应脚本加载完成的事件。

  5. 预加载关键资源

    通过预加载关键资源(如字体文件、重要的CSS和JavaScript文件等),可以在首屏展示之前提前加载这些资源,加快后续页面加载的速度。

    要实现预加载关键资源,可以使用HTML中的linkscript标签的rel属性来指定资源的加载方式。

    举个示例,演示如何预加载字体文件、CSS和JavaScript文件:

    <!DOCTYPE html>
    <html>
    <head>
      <title>预加载关键资源示例</title>
      <link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
      <link rel="preload" href="styles.css" as="style" crossorigin>
      <link rel="preload" href="script.js" as="script" crossorigin>
      <style>
        /* 首屏样式 */
        body {
          font-family: 'FontName', sans-serif;
        }
      </style>
      <link rel="stylesheet" href="styles.css">
      <script src="script.js" defer></script>
    </head>
    <body>
      <!-- 页面内容 -->
    </body>
    </html>
    

    在上述示例中,我们使用了link标签来预加载字体文件、CSS和script标签来预加载JavaScript文件。具体解释如下:

    • link标签用于预加载字体文件。href属性指定字体文件的URL,as属性设置为"font",表示该资源是字体文件,type属性指定资源的MIME类型,crossorigin属性用于处理跨域请求。
    • 同样地,我们使用link标签预加载CSS文件。as属性设置为"style",表示该资源是样式表。
    • 最后,我们使用script标签预加载JavaScript文件。as属性设置为"script",表示该资源是脚本文件,defer属性用于延迟脚本的执行。

    通过以上代码,浏览器在解析HTML时会提前加载这些关键资源,以加快后续页面加载的速度。

  6. CSS优化

    避免使用过多的CSS文件和行内样式,尽量减少CSS文件的大小。另外,可以将CSS放在页面头部,以便尽早渲染页面。

    <!DOCTYPE html>
    <html>
      <head>
        <title>示例页面</title>
        <link rel="stylesheet" href="styles.css">
      </head>
      <body>
        <h1>欢迎来到示例页面</h1>
        <p>这是一个简单的示例页面。</p>
      </body>
    </html>
    

    这样,浏览器在加载页面时会先加载CSS文件,并根据CSS规则渲染页面,从而提高页面的加载速度和性能。

    另外,还可以避免使用行内样式,因为它会增加HTML文件的大小,使得页面加载更慢。如果必须使用行内样式,也应该尽量减少其数量和大小,以便提高页面性能。

  7. JavaScript优化

    将JavaScript代码放在页面底部,以减少对页面渲染的阻塞。另外,可以通过代码压缩、去除不必要的注释和空格等方式来减小JavaScript文件的大小。

    <!DOCTYPE html>
    <html>
    <head>
      <title>JavaScript代码放在页面底部示例</title>
    </head>
    <body>
      <!-- 页面内容 -->
      <script src="script.js"></script>
    </body>
    </html>
    

    示例中,将script标签放在了body标签的最后。这样做可以让浏览器在加载完页面主要内容后再加载JavaScript文件,从而减少对页面渲染的阻塞。

  8. 缓存策略

    合理设置缓存策略,利用浏览器缓存来减少重复加载相同资源的次数,提高页面加载速度。

    // 假设我们有一个名为 "api.js" 的模块,用于发送网络请求
    
    // 缓存对象,用于存储已经请求过的数据和过期时间
    const cache = {};
    
    // 发送网络请求的函数
    async function fetchData(url) {
      // 检查缓存中是否存在数据
      if (cache[url] && cache[url].expires > Date.now()) {
        console.log('从缓存中获取数据:', cache[url].data);
        return cache[url].data;
      }
    
      // 发送网络请求
      const response = await fetch(url);
      const data = await response.json();
    
      // 设置缓存有效期为1小时
      const expires = Date.now() + 3600000;
    
      // 将数据和过期时间存入缓存
      cache[url] = { data, expires };
    
      console.log('从服务器获取数据:', data);
      return data;
    }
    
    // 在页面中使用 fetchData 函数来获取数据
    async function getData() {
      const url = 'https://api.example.com/data';
    
      try {
        const data = await fetchData(url);
        // 处理数据
        console.log('处理数据:', data);
      } catch (error) {
        console.error('请求失败:', error);
      }
    }
    
    // 调用获取数据的函数
    getData();
    

    示例中,定义了一个 fetchData 函数来发送网络请求,并使用一个名为 cache 的对象用于存储已经请求过的数据和过期时间。

    当调用 fetchData 函数时,它首先检查缓存中是否已经存在对应 URL 的数据和过期时间。如果存在且有效,则直接从缓存中获取数据,并返回。否则,则发送网络请求获取数据,并将数据与有效期时间戳存入缓存中。

    在调用 getData 函数时,它会调用 fetchData 函数来获取数据,并对返回的数据进行处理。

    通过这样的缓存策略,当多次调用 getData 函数时,如果 URL 相同,那么后续的请求会直接从缓存中获取数据,而不需要再次发送网络请求。这样可以减少重复加载相同数据的次数,提高页面加载速度和用户体验。

  9. 服务端渲染(SSR)
    缘由

    SSR(服务器端渲染)能够提高首屏渲染的原因有以下几点:

    1. 减少客户端渲染时间:在传统的客户端渲染中,浏览器需要下载HTML文件,然后执行JavaScript代码来生成并填充页面内容。而在SSR中,服务器会在响应请求时直接生成完整的HTML页面,并包含所需的数据。这样,浏览器只需要下载已经渲染好的HTML,减少了客户端渲染的时间。
    2. 提前获取数据:在客户端渲染中,通常需要通过异步请求获取数据,然后再进行页面渲染。而在SSR中,服务器会在渲染过程中获取所需的数据,并将数据直接注入到HTML中。这样可以避免客户端请求数据的延迟,加快了首屏渲染的速度。
    3. 更好的SEO:由于搜索引擎爬虫更容易解析和索引静态HTML页面,使用SSR可以提供更好的SEO效果。因为在SSR中,服务器返回的是已经渲染好的HTML页面,而不是由JavaScript生成的动态内容。这使得搜索引擎可以更好地理解和索引网页内容,提高了网站在搜索结果中的可见性。
    4. 更好的用户体验:由于SSR能够更快地呈现出首屏内容,用户可以更快地看到页面的基本结构和内容,减少了等待时间,提升了用户体验。
    可选用框架

    前端可以使用一些框架或库来实现服务器端渲染(SSR),下面是几个常用的前端框架:

    1. Next.js:Next.js是一个基于React的SSR框架。它提供了简单的API和约定,使得构建SSR应用变得更加容易。Next.js支持自动代码分割、预取和缓存,以提高性能。它还提供了静态导出功能,可以将应用程序预渲染为静态HTML文件。
    2. Nuxt.js:Nuxt.js是一个基于Vue.js的SSR框架。它提供了类似于Next.js的功能,可以帮助开发者快速构建Vue.js应用的SSR版本。Nuxt.js支持自动路由、代码分割和服务端数据获取等特性。
    3. Angular Universal:Angular Universal是Angular官方提供的SSR解决方案。它允许在服务器上预渲染Angular应用,以提供更好的性能和SEO效果。Angular Universal可以与Angular CLI集成,方便开发者进行构建和部署。
  10. CDN加速

使用内容分发网络(CDN)来加速静态资源的传输,将资源缓存在离用户较近的服务器上,减少网络延迟。

下面以一个网站为例,说明CDN如何工作:

假设有一个名为 www.example.com 的网站,该网站拥有许多静态资源,如图像、CSS文件和JavaScript文件等。这些资源可能存储在网站的主机上,但是由于主机的位置和用户之间的距离不同,因此用户在访问这些资源时可能会遇到网络延迟。

为了解决这个问题,网站所有者可以使用CDN服务。CDN服务通常由多个服务器组成,这些服务器分布在全球各地,每个服务器都缓存了网站的静态资源。当用户请求这些资源时,CDN会将请求路由到最近的服务器,从而减少网络延迟并提高资源加载速度。

例如,当用户从中国访问 www.example.com 时,CDN会将用户请求路由到位于中国的服务器。如果该服务器上已经缓存了所需的静态资源,那么用户将立即获得资源。否则,CDN将从主机上获取资源,并将其缓存到服务器上,以便以后的请求可以更快地获取资源。

总之,使用CDN可以提高网站性能,减少网络延迟,提高用户体验。

一般直接在模板中使用script标签src属性引用。 也可以在项目中配置基础路径

const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  // 其他配置项...
  
  output: {
    // 设置输出路径和文件名
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    publicPath: 'https://cdn.example.com/' // 设置CDN路径
  },
  
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      // 在HTML模板中使用CDN路径
      cdnPath: 'https://cdn.example.com/'
    }),
    // 其他插件...
  ],
  
  // 其他配置项...
};
  1. 延迟加载非关键资源

    将非关键资源(如广告、推荐内容等)的加载延迟到首屏渲染完成之后,以提高首屏展示速度。

    <!DOCTYPE html>
    <html>
    <head>
      <title>延迟加载示例</title>
    </head>
    <body>
      <h1>首屏内容</h1>
      
      <!-- 非关键资源 -->
      <div id="ad-container"></div>
      <script src="ad.js" defer></script>
      
      <script>
        // 首屏渲染完成后执行
        window.addEventListener('load', function() {
          // 加载广告
          var adContainer = document.getElementById('ad-container');
          var adScript = document.createElement('script');
          adScript.src = 'ad.js';
          adContainer.appendChild(adScript);
        });
      </script>
    </body>
    </html>
    
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值