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端、移动端比较好的一种方式。

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

相关推荐
cos1 小时前
从像素到粒子:p5.js 图像转动态粒子的设计与实现
前端·javascript·webgl
造糖主义3 小时前
vue-el-upload上传组件自定义删除-预览按钮遮罩层,不受原有的上传打开文件夹
前端·javascript·vue.js
小猫会后空翻4 小时前
XSS相关理解
前端·xss
brzhang4 小时前
我十几个项目都是这套Flutter 快速开发框架,今天开源了,从此你只用关心业务了
前端·后端·架构
qziovv4 小时前
控制Vue对话框显示隐藏
javascript·vue.js·elementui
小宁爱Python5 小时前
TypeScript 泛型详解:从基础到实战应用
前端·javascript·typescript
鱼樱前端5 小时前
一文讲解时下比较火的Rust语言之-rust开发环境搭建
前端·javascript
midsummer_woo6 小时前
基于springboot+vue+mysql的作业管理系统(源码+论文)
vue.js·spring boot·mysql
Moment6 小时前
Next.js 15.4 正式发布:Turbopack 全面稳定,预热 Next.js 16 😍😍😍
前端·javascript·node.js