Nuxt3中PC端与移动端适配的三种方式(含Nuxt官网同款实现方式)

前言

在网站开发中,我们通常需要PC端和移动端适配不同的页面,例如,Nuxt 官网,当宽度大于1024px时展示PC界面,小于时展示移动端。(注:如果你的网站页面只有一套代码,只是通过css样式适配手机端,下面方法对你帮助不大)

适配方案解析

方法一:根据设备类型动态切换组件

通过判断设备类型,动态渲染 PC 端或移动端组件。

实现代码

vue 复制代码
<template>
  <ClientOnly>
    <component :is="isMobile ? Mobile : Desktop" />
  </ClientOnly>
</template>
<script setup>

import Desktop from "~/components/Desktop.vue";
import Mobile from "~/components/Mobile.vue";
const isMobile = ref(false);
//屏幕尺寸改变
const handleResize = () => {
  isMobile.value = getDeviceType() === 'mobile'
}
//监听屏幕宽度
onMounted(() => {
  handleResize();
  window.addEventListener("resize", handleResize)
})

//获取设备类型
const getDeviceType = () => {
  let UA
  if (process.client) {
    // 如果是在客户端执行,则通过 navigator 获取 user-agent
    UA = navigator.userAgent
  } else {
    // 如果是在服务端执行,则通过请求头获取 user-agent
    UA = useRequestHeader('user-agent')
  }
  let type;
  if (/(Android|webOS|iPhone|iPod|tablet|BlackBerry|Mobile)/i.test(UA)) {
    type = 'mobile'
  } else {
    type = 'desktop'
  }
  return type
}
</script>

效果

从效果中可以看出,能够识别PC端移动端,但在移动端刷新时会闪屏PC端页面,并且项目generate之后,刷新这个页面还会出现水合问题"Hydration node mismatch"

这是因为,服务端渲染(SSR)与客户端渲染不一致造成的,如果我们启用了SSR,Nuxt会提前为我们预渲染生成静态代码,而预渲染时是不知道设备类型的,导致预渲染时使用默认端,代码中 isMobile 默认是 false,因此预渲染的静态代码中也就是电脑端代码,从浏览器源代码中可以看出。

解决方法: 将整个组件使用<ClientOnly>包裹,只在客户端渲染。

xml 复制代码
<template> 
    <ClientOnly> <component :is="isMobile ? Mobile : Desktop" /> </ClientOnly>
</template>

这样虽然能能够解决闪屏和水合问题,但这样出来的页面body里面没有内容,对于SEO非常不友好,不推荐使用

方法二:通过路由配置动态切换页面

将 PC 端和移动端页面分别放在独立目录,通过路由配置动态匹配设备类型对应的页面路径。当我们访问/abc时,通过路由router options 动态修改路由,跳转到/mobile/abc或者/desktop/abc中。

实现结构

txt 复制代码
pages/
├─ desktop/
│  └─ index.vue  # PC 端首页
└─ mobile/
└─ index.vue  # 移动端首页

路由配置(app/router.options.ts)

typescript 复制代码
import type {RouterConfig} from '@nuxt/schema';
import {useDeviceType} from '@/composables/useDeviceType';

export default <RouterConfig> {
  routes: (_routes) => {
  const targetType = useDeviceType().value; // 获取设备类型(mobile/desktop)
  const notTargetType = targetType === 'mobile' ? 'desktop' : 'mobile';

// 匹配当前设备的路由(移除前缀)
  const targetRoutes = _routes
  .filter(item => (item.name as string).startsWith(targetType))
  .map(item => ({
  ...item,
  path: item.path.replace(`/${targetType}`, '') || '/'
}));

// 非当前设备的路由(保留前缀)
  const notTargetRoutes = _routes
  .filter(item => (item.name as string).startsWith(notTargetType))
  .map(item => ({
  ...item,
  path: !item.path.startsWith(`/${notTargetType}`)
  ? `/${notTargetType}${item.path}`
  : item.path
}));

  return [...targetRoutes, ...notTargetRoutes];
}
};

效果

存在问题

  1. 预渲染冲突:nuxt 默认会在构建时访问这些路由得到渲染结果,而这种方法会动态修改路由,得到的渲染结果的文件夹页面路径会是混乱的,无法生成静态页面。
  2. 页面缺失风险:若 PC 端与移动端页面数量不一致(如 PC 有 /abc 页面而移动端无),移动端访问 /abc 会出现 404 错误。
  3. 维护成本高:需严格保持两端页面目录结构一致,否则易出现路由匹配异常。

只有在dev环境下可以切换,且每次切换需要刷新页面,不推荐使用

方法三:基于媒体查询的样式控制(推荐)

将 PC 端和移动端组件同时引入页面,通过 CSS 媒体查询控制显示 / 隐藏,两端组件同时渲染在 DOM 中,仅通过样式控制显示,避免组件动态切换导致的内容重绘闪烁,实现无闪屏切换(Nuxt 官网同款方案)。

实现代码

xml 复制代码
<template>
  <div class="desktop-container">
    <Desktop />
  </div>
  <div class="mobile-container">
    <Mobile />
  </div>
</template>

<script setup>
import Desktop from "~/components/Desktop.vue";
import Mobile from "~/components/Mobile.vue";
import { useHead } from "nuxt/app";

// 配置视口(确保适配准确性)
useHead({
  meta: [
    {
      name: "viewport",
      content: "width=device-width, initial-scale=1.0"
    }
  ]
});
</script>

<style scoped>
/* 默认显示 PC 端,隐藏移动端 */
.desktop-container { display: block; }
.mobile-container { display: none; }

/* 屏幕宽度 ≤1024px 时切换为移动端 */
@media (max-width: 1024px) {
  .desktop-container { display: none; }
  .mobile-container { display: block; }
}
</style>

效果

这种方法使用 display 属性来控制不同屏幕尺寸下组件的显示与隐藏,能够依据屏幕尺寸的变化切换对应的组件。预渲染页面里也涵盖了完整的源代码,对 SEO 很友好。不过,源代码中会同时存在移动端和 PC 端的代码,但这对整体的影响并不大,所以还是推荐使用这种方案。

要留意 head 中是否存在 width=device-width, initial-scale=1 的设置,一般情况下是默认有的。要是没有的话,就需要添加以下这段代码:

js 复制代码
useHead({ meta: [ { name: "viewport", content: "width=device-width, initial-scale=1" } ] })
  • width:该参数用于设定视口的宽度,可以将其设置为具体的像素值,也可以设置为 device-width(也就是设备屏幕的宽度)。例如 width=device-width,它能够让页面宽度自动去适配设备的屏幕宽度。
  • initial-scale:此参数用于设置页面首次加载时的缩放比例,1.0 表示不进行缩放。比如 initial-scale=1.0,它可以确保页面按照原始尺寸正常显示,不会有默认的缩放情况。

在 safari 浏览器中,viewport 的默认宽度为 980px,要是没有指定 viewport 的宽度,就会默认按照 980px 来处理。当监测到屏幕尺寸小于 980px 并切换页面时,可能会出现闪屏 PC 端页面的情况,这是因为浏览器会先清除原有的设置,使用默认的 viewport,然后再根据媒体查询进行渲染。

所以,这里推荐使用 980px 或者 1024px 作为临界值。1024px 是平板 viewport 的默认宽度,如果希望平板使用手机端的页面,那么就使用 1024px 作为临界值;如果希望平板使用 PC 端的页面,那么就使用 980px。Nuxt 采用的就是这种方式,并且将临界值设定为了 1024px。

方案对比总结

方案 核心原理 优点 缺点 适用场景
方法一 动态切换组件 实现简单,组件隔离清晰 存在闪屏和水合问题,影响 SEO 非 SSR 项目,或对 SEO 要求极低的场景
方法二 动态修改路由路径 路由层级清晰,完全隔离两端页面 预渲染不支持,易出现 404,维护成本高 纯客户端渲染项目,且两端页面结构差异极大
方法三(推荐) 媒体查询控制显示 / 隐藏 无闪屏,支持 SSR / 预渲染,SEO 友好 DOM 中存在冗余节点(影响小) 需独立组件适配,且重视体验和 SEO 的项目

因此,兼顾体验、兼容性和 SEO,使用媒体查询控制是 Nuxt 等 SSR 框架兼容PC端、移动端比较好的一种方式。

以上就是全部内容了,如果你还有比较好的方式或实现思路欢迎评论区讨论。

相关推荐
AI大模型-小华23 分钟前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子3 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅3 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni3 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学4 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
赵庆明老师5 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim
wc885 小时前
微软EDGE浏览器功能学习
前端·学习·edge
Csvn5 小时前
🎯 原生 `<dialog>` 元素:终于可以扔掉一半的自定义弹窗组件了?
前端
Csvn5 小时前
🎨 CSS @layer:用「层叠优先级」终结样式打架的世纪难题
前端