HiHuo
首页
博客
手册
工具
首页
博客
手册
工具
  • Vue & CSS 进阶

    • 第0章:工具链与开发体验
    • 第1章:TypeScript 核心
    • 第2章:CSS 现代能力
    • 第3章:Vue3 核心与原理
    • 第4章:路由与状态管理
    • 第5章:工程化与性能
    • 第6章:测试与质量
    • 第7章:CSS 进阶专题
    • 第8章:项目实战A - SaaS仪表盘
    • 第9章:项目实战B - 内容社区
    • 第10章:Vue 内核深入
    • 第11章:微前端与部署
    • CSS 变量体系与主题系统深度指南
    • 前端安全与防护专题
    • CSS 专题:动效优化与微交互
    • CSS 专题:图片与图形处理
    • 实时通信与WebSocket专题
    • 开发工具与调试技巧专题
    • 新兴技术与未来趋势专题
    • 移动端开发与PWA专题
    • 附录A:代码片段库
    • 附录B:练习题集

CSS 专题:图片与图形处理

掌握现代 CSS 图片处理技术,实现高性能的视觉体验

📚 专题目标

通过本专题学习,你将掌握:

  • 响应式图片加载策略
  • 图片优化与压缩技术
  • 背景混合与遮罩技巧
  • 图形绘制与动画效果
  • 性能优化最佳实践

🖼️ 响应式图片加载

基础响应式图片

<!-- 基础响应式图片 -->
<img
  src="image-400w.jpg"
  srcset="
    image-400w.jpg 400w,
    image-800w.jpg 800w,
    image-1200w.jpg 1200w
  "
  sizes="(max-width: 600px) 400px, (max-width: 900px) 800px, 1200px"
  alt="响应式图片"
  loading="lazy"
/>

<!-- 艺术方向控制 -->
<picture>
  <source
    media="(max-width: 600px)"
    srcset="image-mobile.jpg"
  />
  <source
    media="(max-width: 1200px)"
    srcset="image-tablet.jpg"
  />
  <img
    src="image-desktop.jpg"
    alt="艺术方向图片"
    loading="lazy"
  />
</picture>

<!-- 格式选择 -->
<picture>
  <source
    srcset="image.avif"
    type="image/avif"
  />
  <source
    srcset="image.webp"
    type="image/webp"
  />
  <img
    src="image.jpg"
    alt="格式选择图片"
    loading="lazy"
  />
</picture>

CSS 背景图片响应式

/* 基础响应式背景 */
.hero {
  background-image: url('hero-mobile.jpg');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

@media (min-width: 768px) {
  .hero {
    background-image: url('hero-tablet.jpg');
  }
}

@media (min-width: 1200px) {
  .hero {
    background-image: url('hero-desktop.jpg');
  }
}

/* 使用 image-set() */
.hero {
  background-image: image-set(
    url('hero-mobile.jpg') 1x,
    url('hero-mobile-2x.jpg') 2x,
    url('hero-mobile-3x.jpg') 3x
  );
}

/* 现代浏览器支持 */
@supports (background-image: image-set('test.jpg' 1x)) {
  .hero {
    background-image: image-set(
      url('hero-mobile.jpg') 1x,
      url('hero-mobile-2x.jpg') 2x
    );
  }
}

图片容器组件

<template>
  <div class="image-container" :class="containerClass">
    <img
      :src="src"
      :srcset="srcset"
      :sizes="sizes"
      :alt="alt"
      :loading="loading"
      :decoding="decoding"
      class="responsive-image"
      @load="onLoad"
      @error="onError"
    />
    
    <!-- 加载状态 -->
    <div v-if="loading" class="image-loading">
      <div class="loading-spinner"></div>
    </div>
    
    <!-- 错误状态 -->
    <div v-if="error" class="image-error">
      <Icon name="image-error" />
      <p>图片加载失败</p>
    </div>
  </div>
</template>

<script setup lang="ts">
interface Props {
  src: string
  srcset?: string
  sizes?: string
  alt: string
  loading?: 'lazy' | 'eager'
  decoding?: 'async' | 'sync' | 'auto'
  aspectRatio?: string
  objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down' | 'none'
  placeholder?: string
}

const props = withDefaults(defineProps<Props>(), {
  loading: 'lazy',
  decoding: 'async',
  objectFit: 'cover'
})

const loading = ref(true)
const error = ref(false)

const containerClass = computed(() => ({
  [`aspect-${props.aspectRatio}`]: props.aspectRatio,
  'image-container--loading': loading.value,
  'image-container--error': error.value
}))

const onLoad = () => {
  loading.value = false
  error.value = false
}

const onError = () => {
  loading.value = false
  error.value = true
}
</script>

<style scoped>
.image-container {
  position: relative;
  overflow: hidden;
  background-color: #f5f5f5;
}

.responsive-image {
  width: 100%;
  height: 100%;
  object-fit: v-bind(objectFit);
  transition: opacity 0.3s ease;
}

.image-container--loading .responsive-image {
  opacity: 0;
}

.image-loading {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.loading-spinner {
  width: 24px;
  height: 24px;
  border: 2px solid #e5e5e5;
  border-top: 2px solid #007bff;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

.image-error {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  color: #666;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* 宽高比容器 */
.aspect-square { aspect-ratio: 1 / 1; }
.aspect-video { aspect-ratio: 16 / 9; }
.aspect-4-3 { aspect-ratio: 4 / 3; }
.aspect-3-2 { aspect-ratio: 3 / 2; }
</style>

🎨 背景混合与遮罩

背景混合模式

/* 基础混合模式 */
.hero {
  background-image: 
    linear-gradient(45deg, rgba(255, 0, 0, 0.5), rgba(0, 0, 255, 0.5)),
    url('hero-bg.jpg');
  background-size: cover;
  background-position: center;
  background-blend-mode: multiply;
}

/* 多种混合效果 */
.blend-multiply { background-blend-mode: multiply; }
.blend-screen { background-blend-mode: screen; }
.blend-overlay { background-blend-mode: overlay; }
.blend-soft-light { background-blend-mode: soft-light; }
.blend-hard-light { background-blend-mode: hard-light; }
.blend-color-dodge { background-blend-mode: color-dodge; }
.blend-color-burn { background-blend-mode: color-burn; }
.blend-difference { background-blend-mode: difference; }
.blend-exclusion { background-blend-mode: exclusion; }

/* 渐变遮罩 */
.gradient-mask {
  background: 
    linear-gradient(
      to bottom,
      rgba(0, 0, 0, 0.7) 0%,
      rgba(0, 0, 0, 0.3) 50%,
      rgba(0, 0, 0, 0.8) 100%
    ),
    url('image.jpg');
  background-size: cover;
  background-position: center;
}

/* 径向渐变遮罩 */
.radial-mask {
  background: 
    radial-gradient(
      circle at center,
      rgba(255, 255, 255, 0.8) 0%,
      rgba(255, 255, 255, 0.4) 50%,
      rgba(0, 0, 0, 0.6) 100%
    ),
    url('image.jpg');
  background-size: cover;
  background-position: center;
}

图像遮罩技术

/* 基础遮罩 */
.masked-image {
  mask-image: url('mask.svg');
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;
  -webkit-mask-image: url('mask.svg');
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
}

/* 渐变遮罩 */
.gradient-mask {
  mask-image: linear-gradient(
    to bottom,
    black 0%,
    transparent 100%
  );
  -webkit-mask-image: linear-gradient(
    to bottom,
    black 0%,
    transparent 100%
  );
}

/* 圆形遮罩 */
.circle-mask {
  mask-image: radial-gradient(circle, black 50%, transparent 50%);
  -webkit-mask-image: radial-gradient(circle, black 50%, transparent 50%);
}

/* 文字遮罩效果 */
.text-mask {
  background: url('pattern.jpg');
  background-size: cover;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  font-size: 4rem;
  font-weight: bold;
}

毛玻璃效果

/* 基础毛玻璃 */
.glass-effect {
  background: rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 12px;
}

/* 增强毛玻璃 */
.enhanced-glass {
  background: rgba(255, 255, 255, 0.15);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.3);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}

/* 彩色毛玻璃 */
.colored-glass {
  background: linear-gradient(
    135deg,
    rgba(255, 0, 150, 0.1),
    rgba(0, 204, 255, 0.1)
  );
  backdrop-filter: blur(15px);
  -webkit-backdrop-filter: blur(15px);
  border: 1px solid rgba(255, 255, 255, 0.2);
}

/* 动态毛玻璃 */
.animated-glass {
  background: rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: all 0.3s ease;
}

.animated-glass:hover {
  background: rgba(255, 255, 255, 0.2);
  backdrop-filter: blur(15px);
  -webkit-backdrop-filter: blur(15px);
  transform: translateY(-2px);
}

🎭 图形绘制与动画

CSS 图形绘制

/* 基础几何图形 */
.circle {
  width: 100px;
  height: 100px;
  background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
  border-radius: 50%;
}

.triangle {
  width: 0;
  height: 0;
  border-left: 50px solid transparent;
  border-right: 50px solid transparent;
  border-bottom: 100px solid #ff6b6b;
}

.diamond {
  width: 100px;
  height: 100px;
  background: #4ecdc4;
  transform: rotate(45deg);
}

.heart {
  width: 100px;
  height: 90px;
  position: relative;
  transform: rotate(-45deg);
}

.heart::before,
.heart::after {
  content: '';
  width: 52px;
  height: 80px;
  position: absolute;
  left: 50px;
  top: 0;
  background: #ff6b6b;
  border-radius: 50px 50px 0 0;
  transform: rotate(-45deg);
  transform-origin: 0 100%;
}

.heart::after {
  left: 0;
  transform: rotate(45deg);
  transform-origin: 100% 100%;
}

/* 复杂图形 */
.star {
  width: 0;
  height: 0;
  border-left: 50px solid transparent;
  border-right: 50px solid transparent;
  border-bottom: 35px solid #ffd700;
  position: relative;
}

.star::before {
  content: '';
  width: 0;
  height: 0;
  border-left: 50px solid transparent;
  border-right: 50px solid transparent;
  border-top: 35px solid #ffd700;
  position: absolute;
  left: -50px;
  top: 10px;
}

/* 使用 clip-path */
.custom-shape {
  width: 200px;
  height: 200px;
  background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
  clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
}

图片动画效果

/* 悬停放大 */
.hover-zoom {
  transition: transform 0.3s ease;
  overflow: hidden;
}

.hover-zoom img {
  transition: transform 0.3s ease;
}

.hover-zoom:hover img {
  transform: scale(1.1);
}

/* 悬停旋转 */
.hover-rotate {
  transition: transform 0.3s ease;
}

.hover-rotate:hover {
  transform: rotate(5deg);
}

/* 悬停倾斜 */
.hover-skew {
  transition: transform 0.3s ease;
}

.hover-skew:hover {
  transform: skew(-5deg, -5deg);
}

/* 悬停翻转 */
.hover-flip {
  perspective: 1000px;
}

.hover-flip-inner {
  transition: transform 0.6s;
  transform-style: preserve-3d;
}

.hover-flip:hover .hover-flip-inner {
  transform: rotateY(180deg);
}

.hover-flip-front,
.hover-flip-back {
  position: absolute;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
}

.hover-flip-back {
  transform: rotateY(180deg);
}

/* 悬停模糊 */
.hover-blur {
  transition: filter 0.3s ease;
}

.hover-blur:hover {
  filter: blur(2px);
}

/* 悬停亮度 */
.hover-brightness {
  transition: filter 0.3s ease;
}

.hover-brightness:hover {
  filter: brightness(1.2);
}

/* 悬停对比度 */
.hover-contrast {
  transition: filter 0.3s ease;
}

.hover-contrast:hover {
  filter: contrast(1.2);
}

/* 悬停饱和度 */
.hover-saturate {
  transition: filter 0.3s ease;
}

.hover-saturate:hover {
  filter: saturate(1.5);
}

图片加载动画

/* 淡入动画 */
.fade-in {
  opacity: 0;
  animation: fadeIn 0.6s ease forwards;
}

@keyframes fadeIn {
  to {
    opacity: 1;
  }
}

/* 从下往上滑入 */
.slide-up {
  opacity: 0;
  transform: translateY(30px);
  animation: slideUp 0.6s ease forwards;
}

@keyframes slideUp {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* 从左往右滑入 */
.slide-left {
  opacity: 0;
  transform: translateX(-30px);
  animation: slideLeft 0.6s ease forwards;
}

@keyframes slideLeft {
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

/* 缩放进入 */
.scale-in {
  opacity: 0;
  transform: scale(0.8);
  animation: scaleIn 0.6s ease forwards;
}

@keyframes scaleIn {
  to {
    opacity: 1;
    transform: scale(1);
  }
}

/* 旋转进入 */
.rotate-in {
  opacity: 0;
  transform: rotate(-180deg);
  animation: rotateIn 0.8s ease forwards;
}

@keyframes rotateIn {
  to {
    opacity: 1;
    transform: rotate(0deg);
  }
}

/* 交错动画 */
.stagger-item {
  opacity: 0;
  transform: translateY(20px);
  animation: staggerIn 0.6s ease forwards;
}

.stagger-item:nth-child(1) { animation-delay: 0.1s; }
.stagger-item:nth-child(2) { animation-delay: 0.2s; }
.stagger-item:nth-child(3) { animation-delay: 0.3s; }
.stagger-item:nth-child(4) { animation-delay: 0.4s; }

@keyframes staggerIn {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

⚡ 性能优化

图片懒加载

/* 基础懒加载样式 */
.lazy-image {
  opacity: 0;
  transition: opacity 0.3s ease;
}

.lazy-image.loaded {
  opacity: 1;
}

/* 占位符样式 */
.image-placeholder {
  background: linear-gradient(
    90deg,
    #f0f0f0 25%,
    #e0e0e0 50%,
    #f0f0f0 75%
  );
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
}

@keyframes loading {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}

/* 渐进式加载 */
.progressive-image {
  position: relative;
  overflow: hidden;
}

.progressive-image img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.progressive-image img:first-child {
  filter: blur(5px);
  transform: scale(1.1);
}

.progressive-image img:last-child {
  opacity: 0;
  transition: opacity 0.3s ease;
}

.progressive-image.loaded img:last-child {
  opacity: 1;
}

图片压缩与优化

/* 响应式图片优化 */
.optimized-image {
  width: 100%;
  height: auto;
  max-width: 100%;
  object-fit: cover;
  object-position: center;
}

/* 图片质量优化 */
.quality-optimized {
  image-rendering: -webkit-optimize-contrast;
  image-rendering: crisp-edges;
  image-rendering: pixelated;
}

/* 图片尺寸优化 */
.size-optimized {
  max-width: 100%;
  height: auto;
  width: auto;
}

/* 图片格式优化 */
.format-optimized {
  /* 使用现代格式 */
  background-image: 
    image-set(
      url('image.avif') type('image/avif'),
      url('image.webp') type('image/webp'),
      url('image.jpg') type('image/jpeg')
    );
}

图片预加载

/* 关键图片预加载 */
.preload-critical {
  position: absolute;
  top: -9999px;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* 图片预加载动画 */
.preload-animation {
  background: linear-gradient(
    90deg,
    #f0f0f0 25%,
    #e0e0e0 50%,
    #f0f0f0 75%
  );
  background-size: 200% 100%;
  animation: preload 1.5s infinite;
}

@keyframes preload {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}

🎯 实战案例

图片画廊组件

<template>
  <div class="image-gallery">
    <div class="gallery-grid">
      <div
        v-for="(image, index) in images"
        :key="image.id"
        class="gallery-item"
        :class="{ 'gallery-item--featured': image.featured }"
        @click="openLightbox(index)"
      >
        <ResponsiveImage
          :src="image.src"
          :alt="image.alt"
          :loading="index < 4 ? 'eager' : 'lazy'"
          class="gallery-image"
        />
        <div class="gallery-overlay">
          <Icon name="zoom" class="gallery-icon" />
        </div>
      </div>
    </div>

    <!-- 灯箱 -->
    <Lightbox
      v-if="lightboxVisible"
      :images="images"
      :current-index="currentIndex"
      @close="closeLightbox"
      @next="nextImage"
      @prev="prevImage"
    />
  </div>
</template>

<script setup lang="ts">
interface Image {
  id: string
  src: string
  alt: string
  featured?: boolean
}

interface Props {
  images: Image[]
}

const props = defineProps<Props>()

const lightboxVisible = ref(false)
const currentIndex = ref(0)

const openLightbox = (index: number) => {
  currentIndex.value = index
  lightboxVisible.value = true
  document.body.style.overflow = 'hidden'
}

const closeLightbox = () => {
  lightboxVisible.value = false
  document.body.style.overflow = ''
}

const nextImage = () => {
  currentIndex.value = (currentIndex.value + 1) % props.images.length
}

const prevImage = () => {
  currentIndex.value = currentIndex.value === 0 
    ? props.images.length - 1 
    : currentIndex.value - 1
}

// 键盘导航
onMounted(() => {
  const handleKeydown = (e: KeyboardEvent) => {
    if (!lightboxVisible.value) return
    
    switch (e.key) {
      case 'Escape':
        closeLightbox()
        break
      case 'ArrowRight':
        nextImage()
        break
      case 'ArrowLeft':
        prevImage()
        break
    }
  }
  
  document.addEventListener('keydown', handleKeydown)
  
  onUnmounted(() => {
    document.removeEventListener('keydown', handleKeydown)
  })
})
</script>

<style scoped>
.image-gallery {
  width: 100%;
}

.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 16px;
}

.gallery-item {
  position: relative;
  aspect-ratio: 4/3;
  overflow: hidden;
  border-radius: 8px;
  cursor: pointer;
  transition: transform 0.3s ease;
}

.gallery-item:hover {
  transform: scale(1.02);
}

.gallery-item--featured {
  grid-column: span 2;
  grid-row: span 2;
}

.gallery-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.3s ease;
}

.gallery-item:hover .gallery-image {
  transform: scale(1.1);
}

.gallery-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.gallery-item:hover .gallery-overlay {
  opacity: 1;
}

.gallery-icon {
  width: 32px;
  height: 32px;
  color: white;
}
</style>

图片编辑器组件

<template>
  <div class="image-editor">
    <div class="editor-toolbar">
      <button @click="applyFilter('none')" :class="{ active: currentFilter === 'none' }">
        原图
      </button>
      <button @click="applyFilter('grayscale')" :class="{ active: currentFilter === 'grayscale' }">
        灰度
      </button>
      <button @click="applyFilter('sepia')" :class="{ active: currentFilter === 'sepia' }">
        复古
      </button>
      <button @click="applyFilter('blur')" :class="{ active: currentFilter === 'blur' }">
        模糊
      </button>
      <button @click="applyFilter('brightness')" :class="{ active: currentFilter === 'brightness' }">
        亮度
      </button>
    </div>
    
    <div class="editor-canvas">
      <img
        ref="imageRef"
        :src="imageSrc"
        :alt="alt"
        :style="imageStyle"
        class="editor-image"
      />
    </div>
    
    <div class="editor-controls">
      <div class="control-group">
        <label>亮度: {{ brightness }}%</label>
        <input
          v-model="brightness"
          type="range"
          min="0"
          max="200"
          class="control-slider"
        />
      </div>
      
      <div class="control-group">
        <label>对比度: {{ contrast }}%</label>
        <input
          v-model="contrast"
          type="range"
          min="0"
          max="200"
          class="control-slider"
        />
      </div>
      
      <div class="control-group">
        <label>饱和度: {{ saturation }}%</label>
        <input
          v-model="saturation"
          type="range"
          min="0"
          max="200"
          class="control-slider"
        />
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
interface Props {
  imageSrc: string
  alt: string
}

const props = defineProps<Props>()

const imageRef = ref<HTMLImageElement>()
const currentFilter = ref('none')
const brightness = ref(100)
const contrast = ref(100)
const saturation = ref(100)

const imageStyle = computed(() => {
  const filters = []
  
  if (currentFilter.value === 'grayscale') {
    filters.push('grayscale(100%)')
  } else if (currentFilter.value === 'sepia') {
    filters.push('sepia(100%)')
  } else if (currentFilter.value === 'blur') {
    filters.push('blur(2px)')
  }
  
  filters.push(`brightness(${brightness.value}%)`)
  filters.push(`contrast(${contrast.value}%)`)
  filters.push(`saturate(${saturation.value}%)`)
  
  return {
    filter: filters.join(' ')
  }
})

const applyFilter = (filter: string) => {
  currentFilter.value = filter
}
</script>

<style scoped>
.image-editor {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.editor-toolbar {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.editor-toolbar button {
  padding: 8px 16px;
  border: 1px solid #ddd;
  background: white;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.editor-toolbar button:hover {
  background: #f5f5f5;
}

.editor-toolbar button.active {
  background: #007bff;
  color: white;
  border-color: #007bff;
}

.editor-canvas {
  text-align: center;
  margin-bottom: 20px;
}

.editor-image {
  max-width: 100%;
  max-height: 400px;
  border-radius: 8px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.editor-controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 20px;
}

.control-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.control-group label {
  font-weight: 500;
  color: #333;
}

.control-slider {
  width: 100%;
  height: 6px;
  border-radius: 3px;
  background: #ddd;
  outline: none;
  -webkit-appearance: none;
}

.control-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #007bff;
  cursor: pointer;
}

.control-slider::-moz-range-thumb {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #007bff;
  cursor: pointer;
  border: none;
}
</style>

🎯 专题总结

通过本专题学习,你掌握了:

  1. 响应式图片:srcset、sizes、picture 元素
  2. 背景混合:混合模式、渐变遮罩、毛玻璃效果
  3. 图像遮罩:mask 属性、文字遮罩、形状遮罩
  4. 图形绘制:CSS 几何图形、clip-path、复杂形状
  5. 图片动画:悬停效果、加载动画、过渡效果
  6. 性能优化:懒加载、预加载、压缩优化
  7. 实战应用:图片画廊、图片编辑器组件

📝 练习题

  1. 实现一个响应式图片画廊,支持懒加载和灯箱预览
  2. 创建一个图片编辑器,支持滤镜和参数调整
  3. 设计一个毛玻璃卡片组件,支持多种混合模式
  4. 实现一个图片上传组件,支持预览和压缩
  5. 创建一个图片轮播组件,支持自动播放和手势控制

🔗 相关资源

  • MDN 图片优化指南
  • Web.dev 图片优化
  • CSS 混合模式
  • CSS 遮罩
  • 图片格式对比
Prev
CSS 专题:动效优化与微交互
Next
实时通信与WebSocket专题