Skip to content

Style 内置样式 - Vant 4

内置样式

介绍

🎨 样式魔法师的工具箱

Vant 就像一位贴心的样式魔法师,为你精心准备了一套实用的样式工具箱!这些内置样式就像是预制的魔法咒语,只需轻轻一挥 className 的魔法棒,就能瞬间为你的界面增添美丽的效果。

无需繁琐的 CSS 编写,无需复杂的样式调试,这些经过精心打磨的样式类就像是你的得力助手,随时待命为你的项目增光添彩! 🌟

📝 文字省略 - 优雅的空间艺术家

✂️ 智能文本裁剪师,让长文本优雅"瘦身"

当文本内容像脱缰的野马一样超出容器边界时,这位贴心的空间艺术家会优雅地出手,用精美的省略号为过长的文字画上完美的句号。无论是一行、两行还是三行,都能完美掌控,让你的界面始终保持整洁美观的视觉效果! 🎭

html
这是一段最多显示一行的文字,多余的内容会被省略 这是一段最多显示两行的文字,多余的内容会被省略  这是一段最多显示三行的文字,多余的内容会被省略

🌟 最佳实践

样式类组合使用

html
<!-- 组合使用多个样式类 -->
<div class="van-ellipsis van-haptic-feedback">
  这是一个既有文字省略又有触碰反馈的元素
</div>

<!-- 安全区 + 1px边框的完美组合 -->
<div class="van-safe-area-bottom van-hairline--top">
  底部安全区域内容
</div>

<!-- 多行省略 + 触碰反馈 -->
<p class="van-multi-ellipsis--l2 van-haptic-feedback">
  这是一段很长的文字内容,会被限制在两行内显示,超出部分用省略号表示,同时具有触碰反馈效果
</p>

响应式样式应用

javascript
// 动态应用样式类
const applyResponsiveStyles = (element, isMobile) => {
  if (isMobile) {
    element.classList.add('van-safe-area-bottom');
    element.classList.add('van-haptic-feedback');
  } else {
    element.classList.remove('van-safe-area-bottom');
    element.classList.add('van-ellipsis');
  }
};

// 根据屏幕尺寸动态调整
const handleResize = () => {
  const isMobile = window.innerWidth <= 768;
  const elements = document.querySelectorAll('.responsive-element');
  
  elements.forEach(el => applyResponsiveStyles(el, isMobile));
};

window.addEventListener('resize', handleResize);

性能优化建议

css
/* 自定义样式时保持一致性 */
.custom-ellipsis {
  /* 继承 Vant 的省略样式 */
  @extend .van-ellipsis;
  
  /* 添加自定义效果 */
  color: var(--van-primary-color);
  font-weight: 500;
}

/* 避免样式冲突 */
.my-component {
  /* 使用 CSS 变量保持主题一致性 */
  --van-text-color: #333;
  --van-border-color: #eee;
}

/* 优化动画性能 */
.smooth-transition {
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  will-change: transform, opacity;
}

💡 使用技巧

智能省略文本处理

javascript
// 动态计算最适合的省略行数
const calculateOptimalLines = (element, maxHeight) => {
  const lineHeight = parseInt(getComputedStyle(element).lineHeight);
  const maxLines = Math.floor(maxHeight / lineHeight);
  
  // 应用对应的省略样式
  const ellipsisClass = maxLines === 1 ? 'van-ellipsis' : 
                       maxLines === 2 ? 'van-multi-ellipsis--l2' :
                       'van-multi-ellipsis--l3';
  
  element.className = ellipsisClass;
  return maxLines;
};

// 文本内容自适应
const adaptiveTextDisplay = (text, container) => {
  const tempElement = document.createElement('div');
  tempElement.textContent = text;
  tempElement.style.visibility = 'hidden';
  tempElement.style.position = 'absolute';
  
  document.body.appendChild(tempElement);
  
  const textHeight = tempElement.offsetHeight;
  const containerHeight = container.offsetHeight;
  
  document.body.removeChild(tempElement);
  
  return calculateOptimalLines(container, containerHeight);
};

高级边框效果

css
/* 渐变边框效果 */
.gradient-hairline {
  position: relative;
  background: linear-gradient(90deg, transparent, var(--van-border-color), transparent);
}

.gradient-hairline::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(
    90deg, 
    transparent 0%, 
    var(--van-border-color) 20%, 
    var(--van-border-color) 80%, 
    transparent 100%
  );
  transform: scaleY(0.5);
}

/* 动态边框效果 */
.animated-border {
  position: relative;
  overflow: hidden;
}

.animated-border::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--van-primary-color), transparent);
  animation: border-slide 2s infinite;
  transform: scaleY(0.5);
}

@keyframes border-slide {
  0% { left: -100%; }
  100% { left: 100%; }
}

创意安全区应用

vue
<template>
  <div class="smart-safe-area">
    <!-- 顶部安全区指示器 -->
    <div class="safe-area-indicator top" v-if="hasTopNotch">
      <div class="notch-simulation"></div>
    </div>
    
    <!-- 内容区域 -->
    <div class="content-area van-safe-area-top van-safe-area-bottom">
      <slot />
    </div>
    
    <!-- 底部安全区指示器 -->
    <div class="safe-area-indicator bottom" v-if="hasBottomSafeArea">
      <div class="home-indicator"></div>
    </div>
  </div>
</template>

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

const hasTopNotch = ref(false);
const hasBottomSafeArea = ref(false);

onMounted(() => {
  // 检测设备安全区
  const safeAreaTop = getComputedStyle(document.documentElement)
    .getPropertyValue('--van-safe-area-inset-top');
  const safeAreaBottom = getComputedStyle(document.documentElement)
    .getPropertyValue('--van-safe-area-inset-bottom');
    
  hasTopNotch.value = parseInt(safeAreaTop) > 0;
  hasBottomSafeArea.value = parseInt(safeAreaBottom) > 0;
});
</script>

<style scoped>
.notch-simulation {
  width: 150px;
  height: 30px;
  background: #000;
  border-radius: 0 0 15px 15px;
  margin: 0 auto;
}

.home-indicator {
  width: 134px;
  height: 5px;
  background: rgba(0, 0, 0, 0.3);
  border-radius: 2.5px;
  margin: 8px auto;
}
</style>

🔧 常见问题解决

省略号不显示问题

javascript
// Q: 为什么省略号样式不生效?
// A: 检查以下几个常见原因

const debugEllipsis = (element) => {
  const styles = getComputedStyle(element);
  
  console.log('调试省略样式:');
  console.log('width:', styles.width);
  console.log('overflow:', styles.overflow);
  console.log('white-space:', styles.whiteSpace);
  console.log('text-overflow:', styles.textOverflow);
  
  // 常见问题检查
  if (styles.width === 'auto') {
    console.warn('⚠️ 元素宽度为 auto,需要设置固定宽度');
  }
  
  if (styles.overflow !== 'hidden') {
    console.warn('⚠️ overflow 不是 hidden');
  }
  
  if (styles.whiteSpace !== 'nowrap' && !element.classList.contains('van-multi-ellipsis')) {
    console.warn('⚠️ 单行省略需要 white-space: nowrap');
  }
};

// 修复省略样式
const fixEllipsis = (element) => {
  element.style.width = '100%';
  element.style.maxWidth = '200px'; // 根据需要调整
  element.style.overflow = 'hidden';
  element.style.textOverflow = 'ellipsis';
  element.style.whiteSpace = 'nowrap';
};

1px边框在某些设备上显示异常

css
/* 解决方案:使用更兼容的实现 */
.reliable-hairline {
  position: relative;
}

.reliable-hairline::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 200%;
  height: 200%;
  border: 1px solid var(--van-border-color);
  transform: scale(0.5);
  transform-origin: 0 0;
  box-sizing: border-box;
  pointer-events: none;
}

/* 针对不同DPR的优化 */
@media (-webkit-min-device-pixel-ratio: 2) {
  .hairline-dpr2::after {
    transform: scale(0.5);
  }
}

@media (-webkit-min-device-pixel-ratio: 3) {
  .hairline-dpr3::after {
    transform: scale(0.33);
  }
}

安全区在某些浏览器不生效

javascript
// 安全区兼容性处理
const setupSafeAreaFallback = () => {
  // 检测是否支持安全区
  const supportsSafeArea = CSS.supports('padding-top: env(safe-area-inset-top)');
  
  if (!supportsSafeArea) {
    // 手动检测设备类型并设置安全区
    const isIPhoneX = /iPhone/.test(navigator.userAgent) && 
                     window.screen.height === 812 && 
                     window.screen.width === 375;
    
    if (isIPhoneX) {
      document.documentElement.style.setProperty('--van-safe-area-inset-top', '44px');
      document.documentElement.style.setProperty('--van-safe-area-inset-bottom', '34px');
    }
  }
};

// 动态安全区检测
const detectSafeArea = () => {
  const testElement = document.createElement('div');
  testElement.style.paddingTop = 'env(safe-area-inset-top)';
  testElement.style.visibility = 'hidden';
  testElement.style.position = 'absolute';
  
  document.body.appendChild(testElement);
  
  const computedPadding = getComputedStyle(testElement).paddingTop;
  const hasSafeArea = computedPadding !== '0px';
  
  document.body.removeChild(testElement);
  
  return hasSafeArea;
};

🎨 设计灵感

创意文字效果

css
/* 彩虹省略号 */
.rainbow-ellipsis {
  position: relative;
}

.rainbow-ellipsis::after {
  content: '...';
  background: linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4, #feca57);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  animation: rainbow-shift 3s ease-in-out infinite;
}

@keyframes rainbow-shift {
  0%, 100% { filter: hue-rotate(0deg); }
  50% { filter: hue-rotate(180deg); }
}

/* 打字机效果省略 */
.typewriter-ellipsis {
  overflow: hidden;
  white-space: nowrap;
  animation: typewriter 3s steps(40) infinite;
}

@keyframes typewriter {
  0% { width: 0; }
  50% { width: 100%; }
  100% { width: 0; }
}

/* 呼吸效果边框 */
.breathing-border::after {
  animation: breathing 2s ease-in-out infinite;
}

@keyframes breathing {
  0%, 100% { opacity: 0.3; transform: scaleY(0.5); }
  50% { opacity: 1; transform: scaleY(1); }
}

互动式安全区

css
/* 可视化安全区 */
.visual-safe-area {
  position: relative;
}

.visual-safe-area::before {
  content: '';
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: env(safe-area-inset-top);
  background: linear-gradient(180deg, 
    rgba(255, 107, 107, 0.1) 0%, 
    transparent 100%);
  pointer-events: none;
  z-index: 9999;
}

.visual-safe-area::after {
  content: '';
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: env(safe-area-inset-bottom);
  background: linear-gradient(0deg, 
    rgba(78, 205, 196, 0.1) 0%, 
    transparent 100%);
  pointer-events: none;
  z-index: 9999;
}

/* 安全区动画指示器 */
.safe-area-pulse {
  animation: safe-area-pulse 2s ease-in-out infinite;
}

@keyframes safe-area-pulse {
  0%, 100% { 
    box-shadow: 0 0 0 0 rgba(var(--van-primary-color-rgb), 0.4);
  }
  50% { 
    box-shadow: 0 0 0 10px rgba(var(--van-primary-color-rgb), 0);
  }
}

高级触碰反馈

css
/* 水波纹触碰效果 */
.ripple-feedback {
  position: relative;
  overflow: hidden;
}

.ripple-feedback::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.5);
  transform: translate(-50%, -50%);
  transition: width 0.6s, height 0.6s;
}

.ripple-feedback:active::before {
  width: 300px;
  height: 300px;
}

/* 弹性触碰效果 */
.elastic-feedback {
  transition: transform 0.2s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

.elastic-feedback:active {
  transform: scale(0.95);
}

/* 发光触碰效果 */
.glow-feedback {
  transition: box-shadow 0.3s ease;
}

.glow-feedback:active {
  box-shadow: 
    0 0 20px rgba(var(--van-primary-color-rgb), 0.5),
    0 0 40px rgba(var(--van-primary-color-rgb), 0.3),
    0 0 60px rgba(var(--van-primary-color-rgb), 0.1);
}

🚀 高级功能扩展

智能样式系统

javascript
// 自适应样式管理器
class AdaptiveStyleManager {
  constructor() {
    this.observers = new Map();
    this.breakpoints = {
      mobile: 768,
      tablet: 1024,
      desktop: 1200
    };
  }
  
  // 注册响应式样式
  registerResponsiveStyle(element, styles) {
    const observer = new ResizeObserver(entries => {
      for (const entry of entries) {
        const { width } = entry.contentRect;
        this.applyResponsiveStyles(element, width, styles);
      }
    });
    
    observer.observe(element);
    this.observers.set(element, observer);
  }
  
  // 应用响应式样式
  applyResponsiveStyles(element, width, styles) {
    // 清除之前的样式
    element.className = element.className
      .split(' ')
      .filter(cls => !cls.startsWith('van-'))
      .join(' ');
    
    // 应用新样式
    if (width <= this.breakpoints.mobile) {
      element.classList.add(...styles.mobile);
    } else if (width <= this.breakpoints.tablet) {
      element.classList.add(...styles.tablet);
    } else {
      element.classList.add(...styles.desktop);
    }
  }
  
  // 销毁观察器
  destroy(element) {
    const observer = this.observers.get(element);
    if (observer) {
      observer.disconnect();
      this.observers.delete(element);
    }
  }
}

// 使用示例
const styleManager = new AdaptiveStyleManager();

styleManager.registerResponsiveStyle(document.querySelector('.adaptive-text'), {
  mobile: ['van-ellipsis', 'van-haptic-feedback'],
  tablet: ['van-multi-ellipsis--l2'],
  desktop: ['van-multi-ellipsis--l3']
});

主题化样式系统

vue
<template>
  <div class="theme-provider" :class="themeClass">
    <div class="style-showcase">
      <div class="demo-card van-hairline--surround van-haptic-feedback">
        <h3 class="van-ellipsis">{{ title }}</h3>
        <p class="van-multi-ellipsis--l2">{{ description }}</p>
      </div>
    </div>
  </div>
</template>

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

const currentTheme = ref('light');

const themeClass = computed(() => `theme-${currentTheme.value}`);

const switchTheme = () => {
  currentTheme.value = currentTheme.value === 'light' ? 'dark' : 'light';
};

defineExpose({ switchTheme });
</script>

<style scoped>
.theme-light {
  --van-text-color: #323233;
  --van-border-color: #ebedf0;
  --van-background-color: #ffffff;
}

.theme-dark {
  --van-text-color: #f7f8fa;
  --van-border-color: #323233;
  --van-background-color: #1e1e1e;
}

.demo-card {
  padding: 16px;
  margin: 16px;
  background: var(--van-background-color);
  border-radius: 8px;
  transition: all 0.3s ease;
}

.demo-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
</style>

📚 延伸阅读

技术文档

设计指南

性能优化

相关组件

🔍 Retina 屏幕的完美主义者,打造发丝般精细的边框

这位精密的线条工匠专门为高清屏幕量身定制,运用巧妙的伪类 transform 魔法,创造出真正的 1 像素边框(hairline)。就像用最细的画笔勾勒轮廓一样,为你的元素添加清晰锐利、毫不模糊的精美边框,让每一条线都完美呈现! ✨

html

🛡️ 安全区 - 贴心的屏幕守护者

📱 全面屏时代的智能适配专家,让内容远离"危险地带"

这位贴心的屏幕守护者深知现代设备的"刘海"、"药丸"、"圆角"等特殊区域,就像一位经验丰富的安全员,智能地为你的元素添加安全区适配。确保重要内容永远不会被遮挡,让你的应用在各种奇形怪状的屏幕上都能完美展现! 🌟

html

🎬 动画 - 生动的视觉魔法师

让静态界面瞬间活起来的动效大师

这位生动的视觉魔法师拥有丰富的动画宝库,通过 transition 组件这个神奇的传送门,你可以轻松召唤各种精美的内置动画效果。从优雅的淡入淡出到活泼的滑动效果,每一个动画都经过精心调校,为你的界面注入生命力和趣味性! 🎭

html

👆 触碰反馈 - 敏感的互动精灵

🎯 让每一次点击都有回应的贴心小助手

这位敏感的互动精灵时刻关注着用户的每一次触碰,当手指轻抚屏幕时,它会立即做出优雅的回应——元素透明度的微妙变化,就像害羞的小精灵眨了眨眼睛。

这种细腻的反馈让用户清楚地知道"我点到了!",特别适合按钮等可点击元素,为交互体验增添一份温暖的人性化触感! 💫

html

🧹 清除浮动 - 布局的整理大师

🔧 专治各种浮动"乱象"的布局清洁工

这位勤劳的布局整理大师专门解决 float 布局带来的各种"浮动乱象"。当元素们像调皮的气球一样到处乱飞时,它会挥舞着神奇的清理魔法棒,让所有浮动元素乖乖归位,恢复页面的整洁有序。一招制敌,让布局重回和谐! ✨

html

基于Vant构建的企业级移动端解决方案