Vue Composition API 与 SVG 雪碧图的高级实践

动态图标切换实现

在现代前端应用中,图标动态切换是常见的交互需求。通过 Composition API 我们可以优雅地实现这一功能。

响应式图标控制

使用 ref 控制 <use> 元素的引用是实现动态切换的核心:

<template>
  <svg class="icon" @click="toggleIcon">
    <use :xlink:href="`#${currentIcon}`" />
  </svg>
</template>

<script setup>
import { ref } from 'vue'

const icons = ['home', 'settings', 'user'] // 可用的图标名称数组
const currentIcon = ref(icons[0])

const toggleIcon = () => {
  const currentIndex = icons.indexOf(currentIcon.value)
  const nextIndex = (currentIndex + 1) % icons.length
  currentIcon.value = icons[nextIndex]
}
</script>

关键点解析

  • xlink:href 属性通过响应式变量动态绑定
  • 点击事件触发图标顺序切换
  • 使用模运算实现循环切换

进阶实现:带状态的图标

<script setup>
import { ref, computed } from 'vue'

const iconState = ref('normal') // normal, hover, active

const currentIcon = computed(() => {
  return `${baseIconName}-${iconState.value}`
})
</script>

实践建议

  1. 图标命名采用 iconname-state 的约定(如 home-active
  2. 使用 computed 派生复杂图标名称
  3. 通过 CSS 类名控制不同状态下的样式

组合式函数封装

将 SVG 雪碧图逻辑抽象为可复用的组合式函数:

useSvgSprite 实现

// useSvgSprite.js
import { ref, computed } from 'vue'

export default function useSvgSprite(initialIcon, availableIcons = []) {
  const currentIcon = ref(initialIcon)
  const icons = ref(availableIcons)
  
  const setIcon = (iconName) => {
    if (!icons.value.includes(iconName)) {
      console.warn(`Icon ${iconName} is not available`)
      return
    }
    currentIcon.value = iconName
  }
  
  const nextIcon = () => {
    const currentIndex = icons.value.indexOf(currentIcon.value)
    const nextIndex = (currentIndex + 1) % icons.value.length
    currentIcon.value = icons.value[nextIndex]
  }
  
  return {
    currentIcon,
    setIcon,
    nextIcon
  }
}

在组件中使用

<script setup>
import useSvgSprite from '@/composables/useSvgSprite'

const { currentIcon, nextIcon } = useSvgSprite('home', ['home', 'search', 'user'])
</script>

封装优势

  1. 统一管理图标切换逻辑
  2. 内置输入验证防止无效图标
  3. 可在多个组件间复用
  4. 易于扩展新功能(如随机切换、动画效果等)

类型增强版本(配合 TypeScript)

// useSvgSprite.ts
import { ref, computed } from 'vue'

type IconName = 'home' | 'search' | 'user' // 联合类型限定可用图标

export default function useSvgSprite(
  initialIcon: IconName,
  availableIcons: IconName[] = []
) {
  // 实现逻辑...
}

性能优化实践

动态加载雪碧图

const loadSprite = async () => {
  if (!document.getElementById('sprite-container')) {
    const response = await fetch('/sprite.svg')
    const svg = await response.text()
    const div = document.createElement('div')
    div.id = 'sprite-container'
    div.style.display = 'none'
    div.innerHTML = svg
    document.body.appendChild(div)
  }
}

// 在组件挂载时调用
onMounted(loadSprite)

缓存策略实现

图1

优化建议

  1. 使用 localStorage 缓存雪碧图内容
  2. 添加版本控制避免缓存过期问题
  3. 大项目考虑按需加载图标分组

常见问题解决方案

1. 图标闪烁问题

原因:雪碧图加载完成前组件已渲染

解决方案

<template>
  <svg v-if="isLoaded" class="icon">
    <use :xlink:href="`#${currentIcon}`" />
  </svg>
  <div v-else class="placeholder"></div>
</template>

<script setup>
const isLoaded = ref(false)

onMounted(() => {
  loadSprite().then(() => {
    isLoaded.value = true
  })
})
</script>

2. 跨组件状态同步

// sharedIconState.js
import { reactive } from 'vue'

const state = reactive({
  currentTheme: 'light',
  icons: {
    light: ['sun', 'moon'],
    dark: ['moon', 'stars']
  }
})

export function useSharedIconState() {
  return {
    state,
    setTheme: (theme) => {
      state.currentTheme = theme
    }
  }
}

总结

通过 Composition API 封装 SVG 雪碧图逻辑,我们获得了:

  1. 更清晰的代码组织 - 将图标相关逻辑集中管理
  2. 更好的复用性 - 通过组合式函数跨组件共享
  3. 更强的类型安全 - 配合 TypeScript 增强可靠性
  4. 更优的性能 - 实现按需加载和智能缓存

进阶方向建议

  • 实现图标过渡动画
  • 开发可视化图标选择器
  • 集成到设计系统中作为基础组件
  • 实现服务端渲染(SSR)兼容方案

评论已关闭