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>
🎯 专题总结
通过本专题学习,你掌握了:
- 响应式图片:srcset、sizes、picture 元素
- 背景混合:混合模式、渐变遮罩、毛玻璃效果
- 图像遮罩:mask 属性、文字遮罩、形状遮罩
- 图形绘制:CSS 几何图形、clip-path、复杂形状
- 图片动画:悬停效果、加载动画、过渡效果
- 性能优化:懒加载、预加载、压缩优化
- 实战应用:图片画廊、图片编辑器组件
📝 练习题
- 实现一个响应式图片画廊,支持懒加载和灯箱预览
- 创建一个图片编辑器,支持滤镜和参数调整
- 设计一个毛玻璃卡片组件,支持多种混合模式
- 实现一个图片上传组件,支持预览和压缩
- 创建一个图片轮播组件,支持自动播放和手势控制