使用React和Vite构建一个AirBnb Experiences克隆网站

这一篇文章中,我会教你如何做一个AirBnb Experiences的克隆网站。主要涵盖React中Props的使用。

克隆网站最终呈现的效果:

1. 使用vite构建基础框架

bash 复制代码
npm create vite@latest

cd airbnb-project
npm install
npm run dev

2. 构建网站的3个部分

网站从上至下主要分为导航栏、简介和活动栏三个部分。

我们在public文件夹之下建立components文件夹,然后分别建立Navbar.jsx,Hero.jsx和Card.jsx这三个文件,分别对应网站的三个部分。

另外,在public文件夹之下建立images文件夹,包含网站要用的所有图片。

photo-grid.png

wedding-photography.png

mountain-bike.png

airbnb-logo.png

katie-zaferes.png

star.png

3. 删除index.css中的所有内容

index.css

css 复制代码
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: 'Poppins', sans-serif;
}

4. 导航栏

Navbar.jsx

javascript 复制代码
export default function Navbar() {
    return (
        <nav>
            <img src="/images/airbnb-logo.png" className="nav--logo"/>
        </nav>
    )
}

对应的index.css中的内容。

css 复制代码
nav {
  height: 70px;
  display: flex;
  padding: 20px 36px;
  box-shadow: 0px 2.98256px 7.4564px rgba(0, 0, 0, 0.1);
}

.nav--logo {
  max-width: 100px;
}

5. 简介栏

Hero.jsx

javascript 复制代码
export default function Hero() {
    return (
        <section className="hero">
            <img src="/images/photo-grid.png" className="hero-photo" />
            <h1 className="hero-header">Online Experiences</h1>
            <p className="hero--text">Join unique interactive activities led by one-of-a-kind hosts-all without leaving home.</p>
        </section>
    )
}

对应的index.css中的内容。

css 复制代码
section {
  padding: 20px;
}

.hero {
  display: flex;
  flex-direction: column;
}

.hero--photo {
  max-width: 400px;
  align-self: center;
}

.hero--header {
  margin-bottom: 16px;
}

.hero--text {
  margin-top: 0;
}

6. 活动栏

Card.jsx

javascript 复制代码
export default function Card(props) {
    let badgeText
    if (props.item.openSpots === 0){
        badgeText = "SOLD OUT"
    } else if (props.item.location === "Online"){
        badgeText = "ONLINE"
    }

    return (
        <div className="card">
            {badgeText && <div className="card--badge">{badgeText}</div>}
            <img 
                src={`/images/${props.item.coverImg}` }
                className="card--image"
                alt="Image of Katie Zaferes."
            />
            <div className="card--stats">
                <img 
                    src="/images/star.png" 
                    className="card--star"
                    alt="Star icon."    
                />
                <span>{props.item.stats.rating}</span>
                <span className="gray">({props.item.stats.reviewCount}) · </span>
                <span className="gray">{props.item.location}</span>
            </div>
            <h2>{props.item.title}</h2>
            <p><span className="bold">From ${props.item.price}</span> / person</p>
        </div>
    )
}

对应的index.css中的内容。

css 复制代码
.card {
  width: 175px;
  font-size: 0.75rem;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  position: relative
}

.card--image {
  width: 100%;
  border-radius: 9px;
  margin-bottom: 9px;
}

.card--title {
  overflow: hidden;
  text-overflow: ellipsis;
}

.card--stats {
  display: flex;
  align-items: center;
}

.card--star {
  height: 14px;
}

.card--price {
  margin-top: auto;
}

.card--badge{
  position: absolute;
  top: 6px;
  left: 6px;
  background-color: white;
  padding: 5px 7px;
  border-radius: 2px;
  font-weight: bold;
}

h2 {
  font-size: 0.75rem;
  font-weight: normal;
}

.gray {
  color: #918E9B;
}

.bold {
  font-weight: bold;
}

7. 删除App.css中的所有内容

8. 准备需要的数据

在src文件夹之下建立data.jsx文件,准备在活动栏中需要呈现的内容。

data.jsx

javascript 复制代码
export default [
    {
        id: 1,
        title: "Life Lessons with Katie Zaferes",
        description: "I will share with you what I call \"Positively Impactful Moments of Disappointment.\" Throughout my career, many of my highest moments only came after setbacks and losses. But learning from those difficult moments is what gave me the ability to rise above them and reach my goals.",
        price: 136,
        coverImg: "katie-zaferes.png",
        stats: {
            rating: 5.0,
            reviewCount: 6
        },
        location: "Online",
        openSpots: 0,
    },
    {
        id: 2,
        title: "Learn Wedding Photography",
        description: "Interested in becoming a wedding photographer? For beginner and experienced photographers alike, join us in learning techniques required to leave the happy couple with memories that'll last a lifetime.",
        price: 125,
        coverImg: "wedding-photography.png",
        stats: {
            rating: 5.0,
            reviewCount: 30
        },
        location: "Online",
        openSpots: 27,
    },
    {
        id: 3,
        title: "Group Mountain Biking",
        description: "Experience the beautiful Norwegian landscape and meet new friends all while conquering rugged terrain on your mountain bike. (Bike provided!)",
        price: 50,
        coverImg: "mountain-bike.png",
        stats: {
            rating: 4.8,
            reviewCount: 2
        },
        location: "Norway",
        openSpots: 3,
    }
]

9. 更新App.jsx中的内容

App.jsx

javascript 复制代码
import Navbar from "../public/components/Navbar"
import Hero from "../public/components/Hero"
import Card from "../public/components/Card"
import data from "./data"

export default function App() {
  const cards = data.map(item => {
    return (
      <Card
        key = {item.id}
        item = {item}
      />
    )
  })
  return (
      <div>
          <Navbar />
          <Hero />
          <section className="cards--list">
            {cards}
          </section>
          
      </div>
  )
}

对应的index.css中的内容。

css 复制代码
.cards--list {
  display: flex;
  flex-wrap: nowrap;
  gap: 20px;
  overflow-x: auto;
}

10. 完整的index.css

css 复制代码
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: 'Poppins', sans-serif;
}

nav {
  height: 70px;
  display: flex;
  padding: 20px 36px;
  box-shadow: 0px 2.98256px 7.4564px rgba(0, 0, 0, 0.1);
}

h2 {
  font-size: 0.75rem;
  font-weight: normal;
}

.gray {
  color: #918E9B;
}

.bold {
  font-weight: bold;
}

.nav--logo {
  max-width: 100px;
}

section {
  padding: 20px;
}

.hero {
  display: flex;
  flex-direction: column;
}

.hero--photo {
  max-width: 400px;
  align-self: center;
}

.hero--header {
  margin-bottom: 16px;
}

.hero--text {
  margin-top: 0;
}

.cards--list {
  display: flex;
  flex-wrap: nowrap;
  gap: 20px;
  overflow-x: auto;
}

.card {
  width: 175px;
  font-size: 0.75rem;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  position: relative
}

.card--image {
  width: 100%;
  border-radius: 9px;
  margin-bottom: 9px;
}

.card--title {
  overflow: hidden;
  text-overflow: ellipsis;
}

.card--stats {
  display: flex;
  align-items: center;
}

.card--star {
  height: 14px;
}

.card--price {
  margin-top: auto;
}

.card--badge{
  position: absolute;
  top: 6px;
  left: 6px;
  background-color: white;
  padding: 5px 7px;
  border-radius: 2px;
  font-weight: bold;
}

11. 上传到github

12. 上传到netlify

相关推荐
林深现海4 小时前
Jetson Orin nano/nx刷机后无法打开chrome/firefox浏览器
前端·chrome·firefox
黄诂多4 小时前
APP原生与H5互调Bridge技术原理及基础使用
前端
前端市界4 小时前
用 React 手搓一个 3D 翻页书籍组件,呼吸海浪式翻页,交互体验带感!
前端·架构·github
早點睡3904 小时前
高级进阶 ReactNative for Harmony 项目鸿蒙化三方库集成实战:react-native-drag-sort
react native·react.js·harmonyos
文艺理科生4 小时前
Nginx 路径映射深度解析:从本地开发到生产交付的底层哲学
前端·后端·架构
千寻girling4 小时前
主管:”人家 Node 框架都用 Nest.js 了 , 你怎么还在用 Express ?“
前端·后端·面试
C澒4 小时前
Vue 项目渐进式迁移 React:组件库接入与跨框架协同技术方案
前端·vue.js·react.js·架构·系统架构
清山博客5 小时前
OpenCV 人脸识别和比对工具
前端·webpack·node.js
要加油哦~5 小时前
AI | 实践教程 - ScreenCoder | 多agents前端代码生成
前端·javascript·人工智能
程序员Sunday5 小时前
说点不一样的。GPT-5.3 与 Claude Opus 4.6 同时炸场,前端变天了?
前端·gpt·状态模式