Code前端首页关于Code前端联系我们

静态图片引入,单文件组件里的直给操作

terry 3天前 阅读数 26 #Vue

在Vue3项目开发里,图片引入看似简单,实际不同场景(比如静态展示、动态切换、组件封装)下藏着不少细节,要是路径处理不对,要么图片显示不出来,要么打包后路径乱套,影响页面效果,今天就把Vue3里引入图片的常见场景、对应方法,还有优化思路拆开来聊聊,帮你避开那些“图片消失术”的坑~

在Vue的单文件组件(SFC)里,处理固定不变的静态图片,核心是让webpack或Vite这类构建工具“认得到”图片资源,常见有两种思路:把图片丢进`src/assets`文件夹,用`import`或`require`引入;或者把图片丢进`public`文件夹,直接用根路径访问。

先看assets文件夹的用法——这个文件夹里的资源会被构建工具解析处理(比如压缩、转base64、加哈希后缀),举个例子,你要在组件里显示src/assets/logo.png,用import的写法是这样:

<template>
  <img :src="logoImg" alt="品牌Logo">
</template>
<script setup>
import logoImg from './assets/logo.png' // 把图片当成模块导入,webpack/Vite会处理路径
</script>

这种方式的好处是,构建工具会自动处理图片路径,不管开发环境还是生产环境,图片都能精准找到位置,要是不想在<script>开头写import,也能用require直接在模板里写:

<template>
  <img :src="require('./assets/logo.png')" alt="品牌Logo">
</template>

原理和import一样,都是告诉构建工具“这个图片是项目依赖,得打包处理”。

再看public文件夹的用法——这个文件夹里的文件不会被构建工具处理,打包时会原封不动拷贝到输出目录(比如dist文件夹),所以引入时,src要写根路径,比如你把图片放在public/images/bg.jpg,组件里这么写:

<template>
  <img src="/images/bg.jpg" alt="页面背景">
</template>

这里的“/”代表项目根目录,不管项目部署到哪里,public里的资源都能通过这个根路径访问到,适合放那些不需要构建处理的固定资源,比如网站图标(favicon.ico)、 robots.txt 文件这些。

总结下assetspublic的区别:assets里的资源会被“深度处理”(压缩、转格式等),public里的资源是“原样输出”,根据图片是否需要构建处理选文件夹,别放错导致路径失效~

动态图片引入:变量、循环里的“灵活应对”

开发中经常遇到图片路径动态变化的情况,比如根据用户操作切换图标、v-for循环渲染列表图片,这时候直接写动态路径容易踩坑,因为构建工具在编译时需要明确知道哪些资源要打包,纯变量拼接的路径它“不认识”。

举个常见场景:做Tab切换组件,每个Tab对应不同图标,图标路径存在数组里,如果直接写动态路径,图片大概率不显示:

<template>
  <div v-for="(item, index) in tabList" :key="index">
    <img :src="item.icon" alt="tab图标">
    <span>{{ item.name }}</span>
  </div>
</template>
<script setup>
import { ref } from 'vue'
const tabList = ref([
  { name: '首页', icon: './assets/home.png' },
  { name: '消息', icon: './assets/message.png' }
])
</script>

问题出在:webpack/Vite没法解析动态的'./assets/home.png'(它不知道这个路径对应的文件是否存在),这时候得用require把路径“包起来”,告诉构建工具“这是要处理的资源”:

修改后:

<template>
  <div v-for="(item, index) in tabList" :key="index">
    <img :src="require(item.icon)" alt="tab图标">
    <span>{{ item.name }}</span>
  </div>
</template>
<script setup>
import { ref } from 'vue'
const tabList = ref([
  { name: '首页', icon: './assets/home.png' },
  { name: '消息', icon: './assets/message.png' }
])
</script>

要是路径里有变量拼接,比如根据用户ID展示不同头像(const avatar = './assets/avatar_' + userId + '.png'),得用模板字符串配合require

<template>
  <img :src="require(`./assets/avatar_${userId}.png`)" alt="用户头像">
</template>
<script setup>
import { ref } from 'vue'
const userId = ref(123)
</script>

但如果是远程图片(比如接口返回的图片URL:https://xxx.com/avatar.jpg),就不用这么麻烦,直接把接口返回的url赋值给img的src就行,因为远程图片不需要构建工具处理,浏览器能直接请求:

<template>
  <img :src="remoteUrl" alt="远程图片">
</template>
<script setup>
import { ref } from 'vue'
const remoteUrl = ref('https://xxx.com/avatar.jpg')
</script>

核心逻辑是:动态路径要让构建工具识别到资源,要么用require包裹,要么确保是远程URL(不需要构建处理)。

组件化场景:封装图片组件时的“传参门道”

很多项目会封装通用Image组件,用来处理加载态、错误 fallback、懒加载这些功能,这时候父组件传src给子组件,得注意路径处理,不然子组件里的图片容易“迷路”。

举个封装MyImage组件的例子,先看错误示范

父组件使用:

<template>
  <MyImage src="./assets/product.png" alt="商品图" />
</template>
<script setup>
import MyImage from './components/MyImage.vue'
</script>

子组件MyImage.vue

<template>
  <img :src="src" :alt="alt" @error="handleError" />
</template>
<script setup>
const props = defineProps({
  src: String,
  alt: String
})
const handleError = () => {
  // 错误时显示占位图
  props.src = '/images/error.png' // 这里直接改props不行,得用ref
}
</script>

这么写会出问题:父组件传的'./assets/product.png'是相对路径字符串,子组件里的img src拿到的是字符串,构建工具没处理过,浏览器根本找不到这个图片。

正确做法是:父组件先把图片路径用importrequire处理成模块,再通过props传给子组件,比如父组件改成:

<template>
  <MyImage :src="productImg" alt="商品图" />
</template>
<script setup>
import MyImage from './components/MyImage.vue'
import productImg from './assets/product.png' // 先导入,让构建工具处理路径
</script>

子组件里直接用接收到的src(此时已经是构建工具处理好的路径):

<template>
  <img :src="src" :alt="alt" @error="handleError" />
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
  src: String,
  alt: String
})
const handleError = () => {
  // 错误时切换为public里的占位图
  const errorImg = ref('/images/error.png')
  // 注意:直接改props不行,需用响应式数据处理,这里简化演示
  // 实际应通过ref或computed管理src
}
</script>

这样处理后,子组件拿到的src已经是构建工具处理好的有效路径,图片就能正常显示,子组件里处理加载态、错误占位时,推荐用public文件夹里的默认图片(比如/images/loading.gif),因为public里的路径稳定,不会因为组件嵌套导致路径错乱。

优化思路:让图片加载更快、打包更轻

图片引入不仅要“能显示”,还要“加载快、体积小”,这部分分享几个实用优化方向:

图片懒加载

Vue3项目里可以用vue-lazyload-next(适配Vue3的懒加载库),安装后在main.js里配置:

import { createApp } from 'vue'
import App from './App.vue'
import VueLazyload from 'vue-lazyload-next'
const app = createApp(App)
app.use(VueLazyload, {
  loading: '/images/loading.gif', // 加载中占位图(放public里)
  error: '/images/error.png' // 加载失败占位图
})
app.mount('#app')

然后在组件里把img的src换成v-lazy指令:

<template>
  <img v-lazy="productImg" alt="商品图" />
</template>
<script setup>
import productImg from './assets/product.png'
</script>

这样图片会在进入视口时才加载,减少页面初始加载的资源压力,尤其适合长列表、轮播图这类图片多的场景。

CDN加速

把图片传到CDN(比如阿里云OSS、七牛云),然后在项目里直接用CDN地址作为图片src,用户访问时,图片会从离自己最近的CDN节点拉取,加载速度更快。

<template>
  <img src="https://cdn.xxx.com/logo.png" alt="品牌Logo" />
</template>

适合那些不常变动的图片(比如品牌Logo、通用图标),既能减轻自己服务器的带宽压力,又能提升用户体验。

打包时的图片优化

如果用Vue CLI(基于webpack),可以通过url-loaderfile-loader配置图片处理规则,比如在vue.config.js里:

module.exports = {
  chainWebpack: config => {
    config.module
      .rule('images')
      .test(/\.(png|jpg|jpeg|gif|svg)$/)
      .use('url-loader')
      .loader('url-loader')
      .options({
        limit: 1024 * 4, // 4kb以下的图片转base64,减少HTTP请求
        name: 'img/[name].[hash:8].[ext]' // 打包后图片的命名规则(加哈希避免缓存)
      })
  }
}

如果用Vite(Vue3推荐的构建工具),它内置了对图片的处理逻辑:小于4kb的图片会转成base64,大于的会作为静态资源输出,不需要额外配置,非常省心。

对于大图(比如首页轮播图),可以考虑用webp格式(兼容性好+体积比jpg/png小20%-30%),或者做响应式图片(根据设备分辨率加载不同尺寸的图片),比如用srcset属性:

<template>
  <img 
    src="banner.jpg" 
    srcset="banner-400.jpg 400w, banner-800.jpg 800w, banner-1200.jpg 1200w" 
    sizes="(max-width: 400px) 400px, (max-width: 800px) 800px, 1200px" 
    alt="首页轮播图"
  >
</template>

这样不同设备会加载对应尺寸的图片,避免手机加载电脑端的大图,浪费流量和性能。

Vue3里引入图片,核心是搞清楚静态/动态场景资源文件夹区别组件传参逻辑,再结合优化手段(懒加载、CDN、打包配置)提升性能,只要把这些细节吃透,不管是做企业官网的静态展示,还是电商项目的动态商品图,图片都能稳稳显示~要是你在开发中遇到图片相关的其他问题,评论区随时交流呀~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门