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>

📚 延伸閱讀

技術文件

設計指南

效能最佳化

相關元件

📏 1px 邊框 - 精密的線條工匠

🔍 Retina 螢幕的完美主義者,打造髮絲般精細的邊框

這位精密的線條工匠專門為高清螢幕量身定製,運用巧妙的偽類 transform 魔法,創造出真正的 1 像素邊框(hairline)。就像用最細的畫筆勾勒輪廓一樣,為您的元素新增清晰銳利、毫不模糊的精美邊框,讓每一條線都完美呈現! ✨

html
<div class="van-clearfix">
  <div style="float: left;">左側浮動內容</div>
  <div style="float: right;">右側浮動內容</div>
</div>

📚 相關內容

🎨 設計與主題

📱 元件文檔

🚀 效能與最佳化

🛠️ 開發工具

🌟 版本資訊

🛡️ 安全區 - 貼心的螢幕守護者

📱 全螢幕時代的智慧適配專家,讓內容遠離「危險地帶」

這位貼心的螢幕守護者深知現代裝置的「瀏海」、「藥丸」、「圓角」等特殊區域,就像一位經驗豐富的安全員,智慧地為您的元素新增安全區適配。確保重要內容永遠不會被遮擋,讓您的應用程式在各種奇形怪狀的螢幕上都能完美展現! 🌟

html
<div class="van-safe-area-top">頂部安全區域內容</div>
<div class="van-safe-area-bottom">底部安全區域內容</div>

🎬 動畫 - 生動的視覺魔法師

讓靜態介面瞬間活起來的動效大師

這位生動的視覺魔法師擁有豐富的動畫寶庫,透過 transition 元件這個神奇的傳送門,您可以輕鬆召喚各種精美的內建動畫效果。從優雅的淡入淡出到活潑的滑動效果,每一個動畫都經過精心調校,為您的介面注入生命力和趣味性! 🎭

html
<van-transition name="fade">
  <div v-show="show">淡入淡出效果</div>
</van-transition>

👆 觸碰回饋 - 敏感的互動精靈

🎯 讓每一次點擊都有回應的貼心小助手

這位敏感的互動精靈時刻關注著使用者的每一次觸碰,當手指輕撫螢幕時,它會立即做出優雅的回應——元素透明度的微妙變化,就像害羞的小精靈眨了眨眼睛。

這種細膩的回饋讓使用者清楚地知道「我點到了!」,特別適合按鈕等可點擊元素,為互動體驗增添一份溫暖的人性化觸感! 💫

html
<div class="van-haptic-feedback">點擊我試試觸碰回饋效果</div>

🧹 清除浮動 - 版面的整理大師

🔧 專治各種浮動「亂象」的版面清潔工

這位勤勞的版面整理大師專門解決 float 版面帶來的各種「浮動亂象」。當元素們像調皮的氣球一樣到處亂飛時,它會揮舞著神奇的清理魔法棒,讓所有浮動元素乖乖歸位,恢復頁面的整潔有序。一招制敵,讓版面重回和諧! ✨

html
<div class="van-clearfix">
  <div style="float: left;">左側浮動內容</div>
  <div style="float: right;">右側浮動內容</div>
</div>

基於Vant構建的企業級移動端解決方案