跳到主要内容

图片与字体

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>
);
}

小结

本章我们学习了:

  1. Image 组件的使用
  2. 远程图片配置
  3. 图片占位符和加载状态
  4. Google 字体和本地字体的使用
  5. 字体 CSS 变量
  6. 图标的使用

练习

  1. 创建一个响应式的图片画廊
  2. 配置并使用 Google 字体
  3. 实现一个带模糊占位符的图片组件
  4. 创建一个带图标的导航栏