超级响应式布局

响应式布局的概念

响应式布局是指网页在不同的设备和屏幕尺寸下,能够自动调整布局和样式,以适应不同的用户需求。

传统前端响应式的问题

在传统的前端项目中,通过屏幕宽度的判断,在CSS中通过媒体查询,来实现不同的布局和样式。但是这种方式存在以下几个问题:

  1. 代码复杂,维护成本高。
  2. 无法实现自适应布局。
  3. 无法实现自适应字体大小。

超级响应式的解决方案

在知鸢宫的响应式布局设计中,体验最好的做法是将响应式布局与自适应布局二者结合:结合 TailWindCSS 用横竖屏媒体查询处理布局切换,以及一些必要断点处的字号数值的变化。同一屏幕方向、同一断点区间内,单位使用 rem 和百分比。除此之外,使用JavaScript,给根节点在相同断点区间、相同屏幕方向内设置线性根字号变化;在不同的断点区间、不同的屏幕方向内,设置字号跳变。

超级响应式的优点

超级响应式的优点主要有以下几个方面: 这套做法可以最灵活的满足包括手机横屏、pc竖屏等小众设计要求的方案,达到任何奇怪的屏幕尺寸都完美显示。

  1. 代码简洁,维护成本低。
  2. 可以实现自适应布局。
  3. 可以实现自适应字体大小。

超级响应式的断点设计

超级响应式布局系统,共可以实现以下12种屏幕尺寸的分别精细化设计:

屏幕大小(可自定义响应点) 屏幕方向 说明 默认设计稿尺寸(可自定义) TailWind CSS 断点前缀
<600px portrait 手机竖屏 375px portrait:
<600px landscape 手机横屏 667px landscape:
600px - 900px portrait 小平板竖屏 576px portrait:xs:
600px - 900px landscape 小平板横屏 1024px landscape:xs:
900px - 1200px portrait 大平板竖屏 720px portrait:sm:
900px - 1200px landscape 大平板横屏 1280px landscape:sm:
1200px - 1500px portrait 小笔记本竖屏 768px portrait:md:
1200px - 1500px landscape 小笔记本横屏 1366px landscape:md:
1500px - 1800px portrait 大笔记本竖屏 900px portrait:lg:
1500px - 1800px landscape 大笔记本横屏 1600px landscape:lg:
>1800px portrait 显示器竖屏 1080px portrait:xl:
>1800px landscape 显示器横屏 1920px landscape:xl:

JavaScipt自适应字号原理

对于两个断点之间的字号的线性变化,我们可以通过以下公式来计算:

\text{rem} = \text{fontSize} \times \left( \frac{\text{docEl.clientWidth}}{\text{designSize}} \right)

此公式的主要目标是根据当前浏览器窗口的宽度(docEl.clientWidth),结合设计稿的尺寸(designSize)和基础字体大小(fontSize),动态地计算出 HTML 根元素(<html>)的字体大小(rem),从而实现页面的响应式布局。

公式各部分含义

  1. fontSize:这是基础字体大小,它是一个固定的值,存储在 window.ZyFontSize 中。在响应式布局中,它作为计算的基础,所有后续的字体大小调整都是基于这个基础值进行的。
  2. docEl.clientWidth:表示当前 HTML 文档根元素(<html>)的可视宽度,即浏览器窗口的宽度。这个值会随着用户调整浏览器窗口大小而动态变化。
  3. designSize:它是根据当前窗口宽度和屏幕方向从 window.ZyDesignSize 中选取的一个合适的设计稿尺寸。不同的窗口宽度范围和屏幕方向对应不同的设计稿尺寸,例如手机竖屏、横屏,平板竖屏、横屏等。

公式计算过程

  1. 计算比例:首先计算 docEl.clientWidthdesignSize 的比值,即 docEl.clientWidth / designSize。这个比值表示当前窗口宽度相对于设计稿尺寸的比例。例如,如果设计稿宽度为 375px,而当前窗口宽度为 750px,那么这个比值就是 2,表示当前窗口宽度是设计稿宽度的 2 倍。
  2. 计算 rem:将基础字体大小 fontSize 乘以这个比例,得到最终的 rem 值。这样,rem 值就会随着窗口宽度的变化而动态调整。例如,如果基础字体大小为 16px,当前窗口宽度是设计稿宽度的 2 倍,那么计算得到的 rem 值就是 16 × 2 = 32px。
  3. 设置根元素字体大小:最后将计算得到的 rem 值加上 px 单位,并设置为 HTML 根元素的字体大小。由于页面中的其他元素可以使用 rem 作为单位来设置字体大小和其他尺寸,因此当根元素的字体大小改变时,整个页面的布局会自动进行响应式调整。

通过这个公式,页面可以根据不同的屏幕尺寸和方向,动态地调整根元素的字体大小,从而实现响应式布局,确保页面在各种设备上都能有良好的显示效果。

技术细节

在项目开发时,可以结合 Tailwind CSS 的responsive modifiers 响应修饰符特性,结合rem 单位,实现页面的响应式布局。例如一个完整的文章列表的 Grid 布局,具体的 Tailwind ClassName 实现方式如下:

css 复制代码
.row-article {
  @apply grid grid-cols-12 portrait:gap-0 portrait:xs:gap-4 gap-4 xs:gap-4 sm:gap-4 md:gap-5 lg:gap-6;
}
.clo-article-card {
  @apply portrait:col-span-12 landscape:col-span-12
          portrait:xs:col-span-12 landscape:xs:col-span-6
          portrait:sm:col-span-6 landscape:sm:col-span-6
          portrait:md:col-span-6 landscape:md:col-span-6
          portrait:lg:col-span-6 landscape:lg:col-span-6
          portrait:xl:col-span-6 landscape:xl:col-span-6;
}

像这样结合使用portrait: 方向断点前缀和xs: 宽度断点前缀,即可分别设置12种屏的样式。

而对于自适应字号,知鸢宫的超级响应式组件ZySuperResponsive ,已经自动处理了通过 JavaScipt 实现动态 HTML 根元素字号大小的设计。只需要开发者编写CSS样式时使用rem 单位,或者直接使用 TailWind CSS 。因为它默认就是采用的rem 单位。

ZySuperResponsive 组件中,自适应字号的核心代码:

typescript 复制代码
const isVertical = () => {
  const mql = window.matchMedia("(orientation: portrait)");
  return mql.matches;
};

const fontSizeInit = () => {
  const docEl = document.documentElement;
  const fontSize = window.ZyFontSize;
  const breakPoints = window.ZyBreakPoints;
  const designSize = window.ZyDesignSize;

  if (docEl) {
    const width = docEl.clientWidth;
    const isVert = isVertical();

    // 定义尺寸映射对象
    const sizeMap = {
      xs: isVert ? designSize.min_xs_v : designSize.min_xs_h,
      sm: isVert ? designSize.xs_sm_v : designSize.xs_sm_h,
      md: isVert ? designSize.sm_md_v : designSize.sm_md_h,
      lg: isVert ? designSize.md_lg_v : designSize.md_lg_h,
      xl: isVert ? designSize.lg_xl_v : designSize.lg_xl_h,
      max: isVert ? designSize.xl_max_v : designSize.xl_max_h,
    };

    let rem;
    if (width <= breakPoints.xs) {
      rem = fontSize * (width / sizeMap.xs);
    } else if (width <= breakPoints.sm) {
      rem = fontSize * (width / sizeMap.sm);
    } else if (width <= breakPoints.md) {
      rem = fontSize * (width / sizeMap.md);
    } else if (width <= breakPoints.lg) {
      rem = fontSize * (width / sizeMap.lg);
    } else if (width <= breakPoints.xl) {
      rem = fontSize * (width / sizeMap.xl);
    } else {
      rem = fontSize * (width / sizeMap.max);
    }

    docEl.style.fontSize = rem + "px";
  }
};

兼容 SSR 服务端渲染的实现方法

该核心方法中使用了document 对象,在 SSR 服务端渲染时不存在document 对象,因此需要通过一些方法使其只在客户端运行。

为了在页面元素及其他页面样式加载之前,就提前自动初始化该样式,并且兼容 SSR 服务端渲染,ZySuperResponsive 组件在服务端渲染时,通过 NUXT3 的useHead() 方法,在 html 头部插入标签,包括window.ZyFontSizewindow.ZyDesignSize 等全局变量,以及一些需要客户端优先加载的样式类脚本,来动态设置 HTML 根元素的字体大小。具体的实现方法如下:

typescript 复制代码
useHead({
  script: [
    {
      innerHTML: `
      window.ZyFontSize = ${props.baseFontSize};
      window.ZyBreakPoints = ${JSON.stringify(props.breakPoints)};
      window.ZyDesignSize = ${JSON.stringify(props.designSize)}
    `,
    },
    {
      innerHTML: `${initZySuperResponsive.toString()};initZySuperResponsive();`,
    },
  ],
})

该标签在浏览器环境会被同步加载,且在 Node.js 环境不会运行。