第七章:项目实战 - 第二节 - Tailwind CSS 响应式官网开发

本节将介绍如何使用 Tailwind CSS 开发一个现代化的响应式企业官网,包括页面布局、组件开发、响应式设计等方面。

页面布局

导航栏组件

typescript 复制代码
// components/Navbar.tsx
import { useState } from 'react';

const Navbar = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <nav className="bg-white shadow-lg fixed w-full top-0 z-50">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="flex justify-between h-16">
          {/* Logo */}
          <div className="flex-shrink-0 flex items-center">
            <img
              className="h-8 w-auto"
              src="/logo.svg"
              alt="Company Logo"
            />
          </div>

          {/* Desktop Menu */}
          <div className="hidden md:flex md:items-center md:space-x-8">
            <a href="#" className="text-gray-700 hover:text-gray-900 px-3 py-2 text-sm font-medium">
              首页
            </a>
            <a href="#" className="text-gray-700 hover:text-gray-900 px-3 py-2 text-sm font-medium">
              产品
            </a>
            <a href="#" className="text-gray-700 hover:text-gray-900 px-3 py-2 text-sm font-medium">
              解决方案
            </a>
            <a href="#" className="text-gray-700 hover:text-gray-900 px-3 py-2 text-sm font-medium">
              关于我们
            </a>
            <button className="ml-8 bg-blue-500 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-600 transition-colors">
              联系我们
            </button>
          </div>

          {/* Mobile Menu Button */}
          <div className="md:hidden flex items-center">
            <button
              onClick={() => setIsOpen(!isOpen)}
              className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100"
            >
              <svg
                className={`${isOpen ? 'hidden' : 'block'} h-6 w-6`}
                stroke="currentColor"
                fill="none"
                viewBox="0 0 24 24"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth="2"
                  d="M4 6h16M4 12h16M4 18h16"
                />
              </svg>
              <svg
                className={`${isOpen ? 'block' : 'hidden'} h-6 w-6`}
                stroke="currentColor"
                fill="none"
                viewBox="0 0 24 24"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth="2"
                  d="M6 18L18 6M6 6l12 12"
                />
              </svg>
            </button>
          </div>
        </div>
      </div>

      {/* Mobile Menu */}
      <div className={`${isOpen ? 'block' : 'hidden'} md:hidden`}>
        <div className="px-2 pt-2 pb-3 space-y-1">
          <a href="#" className="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
            首页
          </a>
          <a href="#" className="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
            产品
          </a>
          <a href="#" className="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
            解决方案
          </a>
          <a href="#" className="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
            关于我们
          </a>
          <button className="w-full bg-blue-500 text-white px-4 py-2 rounded-lg text-base font-medium hover:bg-blue-600 transition-colors">
            联系我们
          </button>
        </div>
      </div>
    </nav>
  );
};

Hero 区域

typescript 复制代码
// components/Hero.tsx
const Hero = () => {
  return (
    <div className="relative bg-white overflow-hidden">
      <div className="max-w-7xl mx-auto">
        <div className="relative z-10 pb-8 bg-white sm:pb-16 md:pb-20 lg:max-w-2xl lg:w-full lg:pb-28 xl:pb-32">
          <main className="mt-10 mx-auto max-w-7xl px-4 sm:mt-12 sm:px-6 md:mt-16 lg:mt-20 lg:px-8 xl:mt-28">
            <div className="sm:text-center lg:text-left">
              <h1 className="text-4xl tracking-tight font-extrabold text-gray-900 sm:text-5xl md:text-6xl">
                <span className="block">打造现代化的</span>
                <span className="block text-blue-600">企业数字体验</span>
              </h1>
              <p className="mt-3 text-base text-gray-500 sm:mt-5 sm:text-lg sm:max-w-xl sm:mx-auto md:mt-5 md:text-xl lg:mx-0">
                我们提供全方位的数字化解决方案,帮助企业实现数字化转型,提升运营效率,创造更大的商业价值。
              </p>
              <div className="mt-5 sm:mt-8 sm:flex sm:justify-center lg:justify-start">
                <div className="rounded-md shadow">
                  <a
                    href="#"
                    className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 md:py-4 md:text-lg md:px-10"
                  >
                    开始使用
                  </a>
                </div>
                <div className="mt-3 sm:mt-0 sm:ml-3">
                  <a
                    href="#"
                    className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-blue-700 bg-blue-100 hover:bg-blue-200 md:py-4 md:text-lg md:px-10"
                  >
                    了解更多
                  </a>
                </div>
              </div>
            </div>
          </main>
        </div>
      </div>
      <div className="lg:absolute lg:inset-y-0 lg:right-0 lg:w-1/2">
        <img
          className="h-56 w-full object-cover sm:h-72 md:h-96 lg:w-full lg:h-full"
          src="/hero-image.jpg"
          alt="Hero Image"
        />
      </div>
    </div>
  );
};

功能组件

特性展示组件

typescript 复制代码
// components/Features.tsx
const features = [
  {
    title: '快速部署',
    description: '提供一站式部署解决方案,快速上线您的业务系统。',
    icon: (
      <svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
      </svg>
    ),
  },
  {
    title: '安全可靠',
    description: '采用业界领先的安全防护措施,保障您的数据安全。',
    icon: (
      <svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
      </svg>
    ),
  },
  // ... 更多特性
];

const Features = () => {
  return (
    <div className="py-12 bg-white">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="lg:text-center">
          <h2 className="text-base text-blue-600 font-semibold tracking-wide uppercase">特性</h2>
          <p className="mt-2 text-3xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-4xl">
            更好的解决方案
          </p>
          <p className="mt-4 max-w-2xl text-xl text-gray-500 lg:mx-auto">
            我们提供全面的解决方案,满足您的各种业务需求。
          </p>
        </div>

        <div className="mt-10">
          <div className="space-y-10 md:space-y-0 md:grid md:grid-cols-2 md:gap-x-8 md:gap-y-10">
            {features.map((feature) => (
              <div key={feature.title} className="relative">
                <div className="absolute flex items-center justify-center h-12 w-12 rounded-md bg-blue-500 text-white">
                  {feature.icon}
                </div>
                <p className="ml-16 text-lg leading-6 font-medium text-gray-900">{feature.title}</p>
                <dd className="mt-2 ml-16 text-base text-gray-500">{feature.description}</dd>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
};

客户案例组件

typescript 复制代码
// components/Cases.tsx
const cases = [
  {
    title: '企业A的数字化转型',
    description: '帮助企业A实现了全面的数字化转型,提升了30%的运营效率。',
    image: '/case-1.jpg',
  },
  // ... 更多案例
];

const Cases = () => {
  return (
    <div className="bg-gray-50 py-12 lg:py-20">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="text-center">
          <h2 className="text-3xl font-extrabold tracking-tight text-gray-900 sm:text-4xl">
            成功案例
          </h2>
          <p className="mt-4 max-w-2xl text-xl text-gray-500 lg:mx-auto">
            看看我们如何帮助客户实现业务增长。
          </p>
        </div>

        <div className="mt-12 grid gap-8 md:grid-cols-2 lg:grid-cols-3">
          {cases.map((case_) => (
            <div
              key={case_.title}
              className="flex flex-col rounded-lg shadow-lg overflow-hidden"
            >
              <div className="flex-shrink-0">
                <img
                  className="h-48 w-full object-cover"
                  src={case_.image}
                  alt={case_.title}
                />
              </div>
              <div className="flex-1 bg-white p-6 flex flex-col justify-between">
                <div className="flex-1">
                  <h3 className="text-xl font-semibold text-gray-900">
                    {case_.title}
                  </h3>
                  <p className="mt-3 text-base text-gray-500">
                    {case_.description}
                  </p>
                </div>
                <div className="mt-6">
                  <a
                    href="#"
                    className="text-base font-semibold text-blue-600 hover:text-blue-500"
                  >
                    了解更多 →
                  </a>
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

响应式优化

媒体查询策略

typescript 复制代码
// styles/breakpoints.ts
export const breakpoints = {
  sm: '640px',   // 小屏幕设备
  md: '768px',   // 平板设备
  lg: '1024px',  // 小型笔记本
  xl: '1280px',  // 大型笔记本
  '2xl': '1536px' // 桌面设备
};

// 使用示例
<div className="
  grid
  grid-cols-1
  gap-6
  sm:grid-cols-2
  lg:grid-cols-3
  xl:grid-cols-4
">
  {/* 内容 */}
</div>

图片响应式处理

typescript 复制代码
// components/ResponsiveImage.tsx
interface ResponsiveImageProps {
  src: string;
  alt: string;
  className?: string;
}

const ResponsiveImage: React.FC<ResponsiveImageProps> = ({
  src,
  alt,
  className = ''
}) => {
  return (
    <picture>
      <source
        media="(min-width: 1024px)"
        srcSet={`${src}?w=1200`}
      />
      <source
        media="(min-width: 768px)"
        srcSet={`${src}?w=800`}
      />
      <img
        src={`${src}?w=400`}
        alt={alt}
        className={`w-full h-auto ${className}`}
        loading="lazy"
      />
    </picture>
  );
};

// 使用示例
<ResponsiveImage
  src="/hero-image.jpg"
  alt="Hero Image"
  className="rounded-lg shadow-lg"
/>

响应式导航

typescript 复制代码
// hooks/useResponsiveNav.ts
import { useState, useEffect } from 'react';

export const useResponsiveNav = () => {
  const [isMenuOpen, setIsMenuOpen] = useState(false);
  const [isMobile, setIsMobile] = useState(false);

  useEffect(() => {
    const checkWidth = () => {
      setIsMobile(window.innerWidth < 768);
      if (window.innerWidth >= 768) {
        setIsMenuOpen(false);
      }
    };

    window.addEventListener('resize', checkWidth);
    checkWidth();

    return () => window.removeEventListener('resize', checkWidth);
  }, []);

  return {
    isMenuOpen,
    setIsMenuOpen,
    isMobile
  };
};

性能优化

图片懒加载

typescript 复制代码
// components/LazyImage.tsx
import { useEffect, useRef, useState } from 'react';

interface LazyImageProps {
  src: string;
  alt: string;
  className?: string;
  placeholder?: string;
}

const LazyImage: React.FC<LazyImageProps> = ({
  src,
  alt,
  className = '',
  placeholder = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
}) => {
  const [isLoaded, setIsLoaded] = useState(false);
  const [imageSrc, setImageSrc] = useState(placeholder);
  const imageRef = useRef<HTMLImageElement>(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            setImageSrc(src);
            observer.unobserve(entry.target);
          }
        });
      },
      {
        rootMargin: '50px'
      }
    );

    if (imageRef.current) {
      observer.observe(imageRef.current);
    }

    return () => {
      if (imageRef.current) {
        observer.unobserve(imageRef.current);
      }
    };
  }, [src]);

  return (
    <img
      ref={imageRef}
      src={imageSrc}
      alt={alt}
      className={`
        transition-opacity duration-300
        ${isLoaded ? 'opacity-100' : 'opacity-0'}
        ${className}
      `}
      onLoad={() => setIsLoaded(true)}
    />
  );
};

组件优化

typescript 复制代码
// components/OptimizedCarousel.tsx
import { memo } from 'react';

interface CarouselProps {
  items: Array<{
    id: string;
    image: string;
    title: string;
  }>;
}

const OptimizedCarousel = memo(({ items }: CarouselProps) => {
  return (
    <div className="relative overflow-hidden">
      <div className="flex snap-x snap-mandatory overflow-x-auto">
        {items.map((item) => (
          <div
            key={item.id}
            className="snap-start flex-shrink-0 w-full md:w-1/2 lg:w-1/3"
          >
            <LazyImage
              src={item.image}
              alt={item.title}
              className="w-full h-64 object-cover"
            />
            <h3 className="mt-2 text-lg font-medium text-gray-900">
              {item.title}
            </h3>
          </div>
        ))}
      </div>
    </div>
  );
});

OptimizedCarousel.displayName = 'OptimizedCarousel';

动画效果

滚动动画

typescript 复制代码
// hooks/useScrollAnimation.ts
import { useEffect, useRef } from 'react';

export const useScrollAnimation = (options = {}) => {
  const elementRef = useRef<HTMLElement>(null);

  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          entry.target.classList.add('animate-fade-in');
          observer.unobserve(entry.target);
        }
      });
    }, options);

    if (elementRef.current) {
      observer.observe(elementRef.current);
    }

    return () => {
      if (elementRef.current) {
        observer.unobserve(elementRef.current);
      }
    };
  }, []);

  return elementRef;
};

// 使用示例
const Section = () => {
  const animationRef = useScrollAnimation({
    threshold: 0.1
  });

  return (
    <div
      ref={animationRef}
      className="opacity-0 transition-opacity duration-1000"
    >
      {/* 内容 */}
    </div>
  );
};

最佳实践

  1. 响应式设计

    • 移动优先设计
    • 合理使用断点
    • 灵活的布局系统
  2. 性能优化

    • 图片优化
    • 组件懒加载
    • 动画性能
  3. 用户体验

    • 交互反馈
    • 加载状态
    • 平滑过渡
  4. 开发建议

    • 组件复用
    • 样式模块化
    • 持续优化
  5. 测试

    • 响应式测试
    • 性能测试
    • 兼容性测试
相关推荐
前端大卫4 分钟前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘20 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare21 分钟前
浅浅看一下设计模式
前端
Lee川24 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼1 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端