图片与字体
Next.js 提供了内置的图片和字体优化功能,可以自动优化资源,提升性能。
图片优化
Image 组件
使用 next/image 组件自动优化图片:
import Image from "next/image";
export default function Avatar() {
return (
<Image
src="/avatar.png"
alt="用户头像"
width={100}
height={100}
className="rounded-full"
/>
);
}
远程图片
需要在 next.config.ts 中配置允许的域名:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "example.com",
port: "",
pathname: "/images/**",
},
],
},
};
export default nextConfig;
使用:
import Image from "next/image";
export default function ProductImage() {
return (
<Image
src="https://example.com/images/product.jpg"
alt="产品图片"
width={500}
height={300}
/>
);
}
填充模式
当不知道图片尺寸时,使用 fill 属性:
import Image from "next/image";
export default function Hero() {
return (
<div className="relative w-full h-64">
<Image
src="/hero.jpg"
alt="Hero image"
fill
className="object-cover"
/>
</div>
);
}
图片占位符
使用 placeholder="blur" 显示模糊占位符:
import Image from "next/image";
export default function Card() {
return (
<Image
src="/product.jpg"
alt="产品图片"
width={400}
height={300}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..."
/>
);
}
对于本地图片,可以自动生成模糊占位符:
import Image from "next/image";
import heroImage from "./hero.jpg";
export default function Hero() {
return (
<Image
src={heroImage}
alt="Hero image"
placeholder="blur"
/>
);
}
图片优先级
首屏图片使用 priority 属性:
import Image from "next/image";
export default function Hero() {
return (
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={600}
priority
/>
);
}
图片加载状态
"use client";
import Image from "next/image";
import { useState } from "react";
export default function ImageWithLoading() {
const [loading, setLoading] = useState(true);
return (
<div className="relative">
{loading && <div className="animate-pulse bg-gray-200 w-full h-64" />}
<Image
src="/photo.jpg"
alt="Photo"
width={400}
height={300}
onLoadingComplete={() => setLoading(false)}
/>
</div>
);
}
字体优化
next/font
Next.js 提供了 next/font 模块自动优化字体:
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="zh-CN">
<body className={inter.className}>{children}</body>
</html>
);
}
Google 字体
import { Roboto, Montserrat, Open_Sans } from "next/font/google";
const roboto = Roboto({
weight: ["400", "700"],
subsets: ["latin"],
});
const montserrat = Montserrat({
subsets: ["latin"],
variable: "--font-montserrat",
});
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh-CN">
<body className={`${roboto.className} ${montserrat.variable}`}>
{children}
</body>
</html>
);
}
本地字体
import localFont from "next/font/local";
const myFont = localFont({
src: [
{
path: "./fonts/MyFont-Regular.woff2",
weight: "400",
style: "normal",
},
{
path: "./fonts/MyFont-Bold.woff2",
weight: "700",
style: "normal",
},
],
variable: "--font-my-font",
});
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh-CN">
<body className={myFont.className}>{children}</body>
</html>
);
}
CSS 变量
使用 CSS 变量应用字体:
import { Inter, Playfair_Display } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
});
const playfair = Playfair_Display({
subsets: ["latin"],
variable: "--font-playfair",
});
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh-CN" className={`${inter.variable} ${playfair.variable}`}>
<body>{children}</body>
</html>
);
}
CSS 中使用:
/* globals.css */
body {
font-family: var(--font-inter), sans-serif;
}
h1, h2, h3 {
font-family: var(--font-playfair), serif;
}
字体预加载
Next.js 自动预加载字体文件,无需额外配置。
图标
使用 SVG 图标
export default function SearchIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
/>
</svg>
);
}
使用图标库
import { HomeIcon, UserIcon, CogIcon } from "@heroicons/react/24/outline";
export default function Navigation() {
return (
<nav>
<a href="/">
<HomeIcon className="w-6 h-6" />
首页
</a>
<a href="/profile">
<UserIcon className="w-6 h-6" />
个人
</a>
<a href="/settings">
<CogIcon className="w-6 h-6" />
设置
</a>
</nav>
);
}
实战示例
响应式图片
import Image from "next/image";
export default function ResponsiveImage() {
return (
<div className="relative w-full aspect-video">
<Image
src="/banner.jpg"
alt="Banner"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover"
priority
/>
</div>
);
}
图片画廊
import Image from "next/image";
const images = [
{ id: 1, src: "/gallery/1.jpg", alt: "图片 1" },
{ id: 2, src: "/gallery/2.jpg", alt: "图片 2" },
{ id: 3, src: "/gallery/3.jpg", alt: "图片 3" },
];
export default function Gallery() {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{images.map((image) => (
<div key={image.id} className="relative aspect-square">
<Image
src={image.src}
alt={image.alt}
fill
className="object-cover rounded-lg"
/>
</div>
))}
</div>
);
}
带字体的卡片
import { Inter, Playfair_Display } from "next/font/google";
import Image from "next/image";
const inter = Inter({ subsets: ["latin"] });
const playfair = Playfair_Display({ subsets: ["latin"] });
export default function ArticleCard({ article }: { article: Article }) {
return (
<article className="bg-white rounded-lg shadow-md overflow-hidden">
<div className="relative h-48">
<Image
src={article.image}
alt={article.title}
fill
className="object-cover"
/>
</div>
<div className="p-4">
<h2 className={`text-xl font-bold ${playfair.className}`}>
{article.title}
</h2>
<p className={`text-gray-600 mt-2 ${inter.className}`}>
{article.excerpt}
</p>
</div>
</article>
);
}
小结
本章我们学习了:
- Image 组件的使用
- 远程图片配置
- 图片占位符和加载状态
- Google 字体和本地字体的使用
- 字体 CSS 变量
- 图标的使用
练习
- 创建一个响应式的图片画廊
- 配置并使用 Google 字体
- 实现一个带模糊占位符的图片组件
- 创建一个带图标的导航栏