Code Monkey home page Code Monkey logo

react-portfolio's Introduction

项目搭建

npx create-react-app my-app
cd my-app
npm install react-bootstrap bootstrap
npm start

导航栏

/src/components/NavBar.js

import { Nav, Navbar, Container } from 'react-bootstrap'
import { useEffect, useState } from 'react'
import logo from '../assets/img/logo.svg'
import navIcon1 from '../assets/img/nav-icon1.svg'
import navIcon2 from '../assets/img/nav-icon2.svg'
import navIcon3 from '../assets/img/nav-icon3.svg'

// 导航栏
export const NavBar = () => {
  // 当前是哪个导航链接高亮
  const [activeLink, setActiveLink] = useState('home')
  // 滚动事件监听, 滚动到不同位置, 不同的导航链接高亮
  const [scrolled, setScrolled] = useState(false)

  useEffect(() => {
    const onScroll = () => {
      if (window.scrollY > 50) {
        setScrolled(true)
      } else {
        setScrolled(false)
      }
    }

    window.addEventListener('scroll', onScroll)

    // 每次设置完后, 取消事件监听
    return () => window.removeEventListener('scroll', onScroll)
  }, [])

  // 点击导航链接事件, 设置高亮
  const onUpdateActiveLink = (value) => {
    console.log(value)
    setActiveLink(value)
  }

  return (
    <Navbar expand="lg" className={scrolled ? 'scrolled' : ''}>
      <Container>
        {/* logo */}
        <Navbar.Brand href="#home">
          <img src={logo} alt="Logo" />
        </Navbar.Brand>
        <Navbar.Toggle aria-controls="basic-navbar-nav">
          <span className="navbar-toggler-icon"></span>
        </Navbar.Toggle>
        <Navbar.Collapse id="basic-navbar-nav">
          <Nav className="me-auto">
            <Nav.Link
              href="#home"
              className={
                activeLink === 'home' ? 'active navbar-link' : 'navbar-link'
              }
              onClick={() => {
                onUpdateActiveLink('home')
              }}>
              Home
            </Nav.Link>
            <Nav.Link
              href="#skills"
              className={
                activeLink === 'skills' ? 'active navbar-link' : 'navbar-link'
              }
              onClick={() => {
                onUpdateActiveLink('skills')
              }}>
              Skills
            </Nav.Link>
            <Nav.Link
              href="#projects"
              className={
                activeLink === 'projects' ? 'active navbar-link' : 'navbar-link'
              }
              onClick={() => {
                onUpdateActiveLink('projects')
              }}>
              Projects
            </Nav.Link>
          </Nav>

          <span className="navbar-text">
            <div className="social-icon">
              <a href="#">
                <img src={navIcon1} alt="" />
              </a>
              <a href="#">
                <img src={navIcon2} alt="" />
              </a>
              <a href="#">
                <img src={navIcon3} alt="" />
              </a>
            </div>

            <button
              className="vvd"
              onClick={() => {
                console.log('connect')
              }}>
              <span>Let's Connect</span>
            </button>
          </span>
        </Navbar.Collapse>
      </Container>
    </Navbar>
  )
}

/src/App.css

/************ Custom Font ************/
@font-face {
  font-family: Centra;
  src: url('./assets/font/CentraNo2-Bold.ttf');
  font-weight: 700;
}
@font-face {
  font-family: Centra;
  src: url('./assets/font/CentraNo2-Medium.ttf');
  font-weight: 500;
}
@font-face {
  font-family: Centra;
  src: url('./assets/font/CentraNo2-Book.ttf');
  font-weight: 400;
}

/************ Default Css ************/
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  scroll-padding-top: 75px;
}

body {
  font-weight: 400;
  overflow-x: hidden;
  position: relative;
  background-color: #121212 !important;
  color: #fff !important;
  font-family: 'Centra', sans-serif !important;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  padding: 0;
  line-height: normal;
}

p,
a,
li,
button,
ul {
  margin: 0;
  padding: 0;
  line-height: normal;
  text-decoration: none;
}

a:hover {
  text-decoration: none;
}

img {
  width: 100%;
  height: auto;
}

button {
  border: 0;
  background-color: transparent;
}

input:focus,
textarea:focus,
select:focus {
  outline: none;
}

@media (min-width: 1700px) {
  main .container {
    max-width: 100%;
    padding: 0 150px;
  }
}

p.success {
  color: green;
}

p.danger {
  color: red;
}
/************ Navbar Css ************/
nav.navbar {
  padding: 18px 0;
  position: fixed;
  width: 100%;
  top: 0;
  z-index: 9999;
  transition: 0.32s ease-in-out;
}
nav.navbar.scrolled {
  padding: 0px 0;
  background-color: #121212;
}
nav.navbar a.navbar-brand {
  width: 9%;
}
nav.navbar .navbar-nav .nav-link.navbar-link {
  font-weight: 400;
  color: #fff !important;
  letter-spacing: 0.8px;
  padding: 0 25px;
  font-size: 18px;
  opacity: 0.75;
}
nav.navbar .navbar-nav a.nav-link.navbar-link:hover,
nav.navbar .navbar-nav a.nav-link.navbar-link.active {
  opacity: 1;
}
span.navbar-text {
  display: flex;
  align-items: center;
}
.social-icon {
  display: inline-block;
  margin-left: 14px;
}
.social-icon a {
  width: 42px;
  height: 42px;
  background: rgba(217, 217, 217, 0.1);
  display: inline-flex;
  border-radius: 50%;
  margin-right: 6px;
  align-items: center;
  justify-content: center;
  line-height: 1;
  border: 1px solid rgba(255, 255, 255, 0.5);
}
.social-icon a::before {
  content: '';
  width: 42px;
  height: 42px;
  position: absolute;
  background-color: #ffffff;
  border-radius: 50%;
  transform: scale(0);
  transition: 0.3s ease-in-out;
}
.social-icon a:hover::before {
  transform: scale(1);
}
.social-icon a img {
  width: 40%;
  z-index: 1;
  transition: 0.3s ease-in-out;
}
.social-icon a:hover img {
  filter: brightness(0) saturate(100%) invert(0%) sepia(7%) saturate(98%) hue-rotate(
      346deg
    )
    brightness(95%) contrast(86%);
}
.navbar-text button {
  font-weight: 700;
  color: #fff;
  border: 1px solid #fff;
  padding: 18px 34px;
  font-size: 18px;
  margin-left: 18px;
  position: relative;
  background-color: transparent;
  transition: 0.3s ease-in-out;
}
.navbar-text button span {
  z-index: 1;
}
.navbar-text button::before {
  content: '';
  width: 0%;
  height: 100%;
  position: absolute;
  background-color: #fff;
  left: 0;
  top: 0;
  z-index: -1;
  transition: 0.3s ease-in-out;
}
.navbar-text button:hover {
  color: #121212;
}
.navbar-text button:hover::before {
  content: '';
  width: 100%;
  height: 100%;
  position: absolute;
}
nav.navbar .navbar-toggler:active,
nav.navbar .navbar-toggler:focus {
  outline: none;
  box-shadow: none;
}
nav.navbar .navbar-toggler-icon {
  width: 24px;
  height: 17px;
  background-image: none;
  position: relative;
  border-bottom: 2px solid #fff;
  transition: all 300ms linear;
  top: -2px;
}
nav.navbar .navbar-toggler-icon:focus {
  border-bottom: 2px solid #fff;
}
nav.navbar .navbar-toggler-icon:after,
nav.navbar .navbar-toggler-icon:before {
  width: 24px;
  position: absolute;
  height: 2px;
  background-color: #fff;
  top: 0;
  left: 0;
  content: '';
  z-index: 2;
  transition: all 300ms linear;
}
nav.navbar .navbar-toggler-icon:after {
  top: 8px;
}
nav.navbar .navbar-toggler[aria-expanded='true'] .navbar-toggler-icon:after {
  transform: rotate(45deg);
  background-color: #fff;
  height: 2px;
}
nav.navbar .navbar-toggler[aria-expanded='true'] .navbar-toggler-icon:before {
  transform: translateY(8px) rotate(-45deg);
  background-color: #fff;
  height: 2px;
}
nav.navbar .navbar-toggler[aria-expanded='true'] .navbar-toggler-icon {
  border-color: transparent;
}

/************ Banner Css ************/
.banner {
  margin-top: 0;
  padding: 260px 0 100px 0;
  background-image: url('./assets/img/banner-bg.png');
  background-position: top center;
  background-size: cover;
  background-repeat: no-repeat;
}
.banner .tagline {
  font-weight: 700;
  letter-spacing: 0.8px;
  padding: 8px 10px;
  background: linear-gradient(
    90.21deg,
    rgba(170, 54, 124, 0.5) -5.91%,
    rgba(74, 47, 189, 0.5) 111.58%
  );
  border: 1px solid rgba(255, 255, 255, 0.5);
  font-size: 20px;
  margin-bottom: 16px;
  display: inline-block;
}
.banner h1 {
  font-size: 65px;
  font-weight: 700;
  letter-spacing: 0.8px;
  line-height: 1;
  margin-bottom: 20px;
  display: block;
}
.banner p {
  color: #b8b8b8;
  font-size: 18px;
  letter-spacing: 0.8px;
  line-height: 1.5em;
  width: 96%;
}
.banner button {
  color: #fff;
  font-weight: 700;
  font-size: 20px;
  margin-top: 60px;
  letter-spacing: 0.8px;
  display: flex;
  align-items: center;
}
.banner button svg {
  font-size: 25px;
  margin-left: 10px;
  transition: 0.3s ease-in-out;
  line-height: 1;
}
.banner button:hover svg {
  margin-left: 25px;
}
.banner img {
  animation: updown 3s linear infinite;
}
@keyframes updown {
  0% {
    transform: translateY(-20px);
  }
  50% {
    transform: translateY(20px);
  }
  100% {
    transform: translateY(-20px);
  }
}
.txt-rotate > .wrap {
  border-right: 0.08em solid #666;
}

/************ Skills Css ************/
.skill {
  padding: 0 0 50px 0;
  position: relative;
}
.skill-bx {
  background: #151515;
  border-radius: 64px;
  text-align: center;
  padding: 60px 50px;
  margin-top: -60px;
}
.skill h2 {
  font-size: 45px;
  font-weight: 700;
}
.skill p {
  color: #b8b8b8;
  font-size: 18px;
  letter-spacing: 0.8px;
  line-height: 1.5em;
  margin: 14px 0 75px 0;
}
.skill-slider {
  width: 80%;
  margin: 0 auto;
  position: relative;
}
.skill-slider .item img {
  width: 50%;
  margin: 0 auto 15px auto;
}
.background-image-left {
  top: 28%;
  position: absolute;
  bottom: 0;
  width: 40%;
  z-index: -4;
}

/************ Projects Css ************/
.project {
  padding: 80px 0;
  position: relative;
  background-color: black;
}
.project h2 {
  font-size: 45px;
  font-weight: 700;
  text-align: center;
}
.project p {
  color: #b8b8b8;
  font-size: 18px;
  letter-spacing: 0.8px;
  line-height: 1.5em;
  margin: 14px auto 30px auto;
  text-align: center;
  width: 56%;
}
.project .nav.nav-pills {
  width: 72%;
  margin: 0 auto;
  border-radius: 50px;
  background-color: rgb(255 255 255 / 10%);
  overflow: hidden;
}
.project .nav.nav-pills .nav-item {
  width: 33.33333%;
}
.project .nav.nav-pills .nav-link {
  background-color: transparent;
  border-radius: 0;
  padding: 17px 0;
  color: #fff;
  width: 100%;
  font-size: 17px;
  letter-spacing: 0.8px;
  font-weight: 500;
  position: relative;
  transition: 0.3s ease-in-out;
  text-align: center;
  z-index: 0;
}
.project .nav.nav-pills .nav-link::before {
  content: '';
  position: absolute;
  width: 0;
  height: 100%;
  background: linear-gradient(90.21deg, #aa367c -5.91%, #4a2fbd 111.58%);
  top: 0;
  left: 0;
  z-index: -1;
  transition: 0.3s ease-in-out;
}
.project .nav.nav-pills .nav-link.active::before {
  width: 100% !important;
}
.project .nav.nav-pills .nav-link.active {
  border: 1px solid rgba(255, 255, 255, 1);
}
.nav-link#projects-tabs-tab-first {
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: 55px 0px 0px 55px;
}
.nav-link#projects-tabs-tab-second {
  border-top: 1px solid rgba(255, 255, 255, 0.5);
  border-bottom: 1px solid rgba(255, 255, 255, 0.5);
}
.nav-link#projects-tabs-tab-third {
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: 0 55px 55px 0;
}
.proj-imgbx {
  position: relative;
  border-radius: 30px;
  overflow: hidden;
  margin-bottom: 24px;
}
.proj-imgbx::before {
  content: '';
  background: linear-gradient(90.21deg, #aa367c -5.91%, #4a2fbd 111.58%);
  opacity: 0.85;
  position: absolute;
  width: 100%;
  height: 0;
  transition: 0.4s ease-in-out;
}
.proj-imgbx:hover::before {
  height: 100%;
}
.proj-txtx {
  position: absolute;
  text-align: center;
  top: 65%;
  left: 50%;
  transform: translate(-50%, -50%);
  transition: 0.5s ease-in-out;
  opacity: 0;
  width: 100%;
}
.proj-imgbx:hover .proj-txtx {
  top: 50%;
  opacity: 1;
}
.proj-txtx h4 {
  font-size: 30px;
  font-weight: 700;
  letter-spacing: 0.8px;
  line-height: 1.1em;
}
.proj-txtx span {
  font-style: italic;
  font-weight: 400;
  font-size: 15px;
  letter-spacing: 0.8px;
}
.background-image-right {
  top: 20%;
  position: absolute;
  bottom: 0;
  width: 35%;
  right: 0;
  z-index: -4;
}

/************ Projects Css ************/
.contact {
  background: linear-gradient(90.21deg, #aa367c -5.91%, #4a2fbd 111.58%);
  padding: 60px 0 200px 0;
}
.contact img {
  width: 92%;
}
.contact h2 {
  font-size: 45px;
  font-weight: 700;
  margin-bottom: 30px;
}
.contact form input,
.contact form textarea {
  width: 100%;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: 20px;
  color: #fff;
  margin: 0 0 8px 0;
  padding: 18px 26px;
  font-weight: 500;
  font-size: 18px;
  letter-spacing: 0.8px;
  transition: 0.3s ease-in-out;
}
.contact form input:focus,
.contact form textarea:focus {
  background: rgba(255, 255, 255, 1);
  color: #121212;
}
.contact form input::placeholder,
.contact form textarea::placeholder {
  font-size: 16px;
  font-weight: 400;
  color: #fff;
}
.contact form input:focus::placeholder,
.contact form textarea:focus::placeholder {
  color: #121212;
  opacity: 0.8;
}
.contact form button {
  font-weight: 700;
  color: #000;
  background-color: #fff;
  padding: 14px 48px;
  font-size: 18px;
  margin-top: 25px;
  border-radius: 0;
  position: relative;
  transition: 0.3s ease-in-out;
}
.contact form button span {
  z-index: 1;
  position: relative;
}
.contact form button:hover {
  color: #fff;
}
.contact form button::before {
  content: '';
  background: #121212;
  width: 0;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 0;
  transition: 0.3s ease-in-out;
}
.contact form button:hover::before {
  width: 100%;
}

/************ Footer Css ************/
.footer {
  padding: 0 0 50px 0;
  background-image: url('./assets/img/footer-bg.png');
  background-position: center center;
  background-size: cover;
  background-repeat: no-repeat;
}
.newsletter-bx {
  background: #ffffff;
  border-radius: 55px;
  color: #121212;
  padding: 85px 125px;
  margin-bottom: 80px;
  margin-top: -122px;
}
.newsletter-bx h3 {
  font-weight: 700;
  letter-spacing: 0.5px;
  line-height: 1.2em;
}
.new-email-bx {
  background: #fff;
  padding: 5px;
  border-radius: 20px;
  position: relative;
  z-index: 0;
  display: flex;
  align-items: center;
}
.new-email-bx::before {
  content: '';
  background: linear-gradient(90.21deg, #aa367c -5.91%, #4a2fbd 111.58%);
  border-radius: 20px;
  position: absolute;
  z-index: -1;
  top: -1px;
  left: -1px;
  bottom: -1px;
  right: -1px;
}
.new-email-bx::after {
  content: '';
  background: #fff;
  border-radius: 20px;
  position: absolute;
  z-index: -1;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
}
.new-email-bx input {
  width: 100%;
  color: #121212;
  font-weight: 500;
  background: transparent;
  border: 0;
  padding: 0 15px;
}
.new-email-bx button {
  background: linear-gradient(90.21deg, #aa367c -5.91%, #4a2fbd 111.58%);
  padding: 20px 65px;
  color: #fff;
  font-weight: 500;
  letter-spacing: 0.5px;
  border-radius: 18px;
}
.footer img {
  width: 26%;
}
.footer p {
  font-weight: 400;
  font-size: 14px;
  color: #b8b8b8;
  letter-spacing: 0.5px;
  margin-top: 20px;
}

/src/App.js

import './App.css'
import 'bootstrap/dist/css/bootstrap.min.css'
import { NavBar } from './components/NavBar'

function App() {
  return (
    <div className="App">
      <NavBar />
    </div>
  )
}

export default App

Main Banner

npm install react-bootstrap-icons --save
# yarn add react-bootstrap-icons

/src/components/Banner.js

import { useState, useEffect } from 'react'
import { Container, Row, Col } from 'react-bootstrap'
import { ArrowRightCircle } from 'react-bootstrap-icons'
import headerImg from '../assets/img/header-img.svg'

export const Banner = () => {
  // 循环次数, 文字数组中第一条、第二条、第三条、第四条 4 % toRotate.length = 1
  const [loopNum, setLoopNum] = useState(0)
  // 当前语句是否打完, 如果打完了从后往前一个一个字符删除
  const [isDeleting, setIsDeleting] = useState(false)
  const toRotate = ['Web Developer', 'Web Designer', 'UI/UX Designer']
  const [text, setText] = useState('') // 当前显示的文字
  const [delta, setDelta] = useState(300 - Math.random() * 100)
  const period = 2000

  // 文字变化就执行
  useEffect(() => {
    let ticker = setInterval(() => {
      tick()
    }, delta)

    return () => {
      clearInterval(ticker)
    }
  }, [text])

  const tick = () => {
    // 第一遍是0
    let i = loopNum % toRotate.length
    let fullText = toRotate[i] // 当前打字动画播放完后, 对应完整的文字
    // 当前动画中的文字
    let updatedText = isDeleting
      ? fullText.substring(0, text.length - 1)
      : fullText.substring(0, text.length + 1)

    // 设置字符
    setText(updatedText)

    // 如果开始删除, 每次删除字符的延迟时间折半(参数默认为上一次的数值),就是越删越快
    if (isDeleting) {
      setDelta((prevDelta) => prevDelta / 2)
    }

    // 如果isDeleting为false 且 当前的文字打完了
    if (!isDeleting && updatedText === fullText) {
      // 开始删除
      setIsDeleting(true)
      setDelta(period) // 初始化是2秒删一个, 不过在上面是越来越快的
    } else if (isDeleting && updatedText === '') {
      // 删完了
      setIsDeleting(false)
      // 播放下一个
      setLoopNum(loopNum + 1)
      setDelta(500) // 设置从第二个开始0.5s打一次字了
    }
  }

  return (
    <section className="banner" id="home">
      <Container>
        <Row className="align-items-center">
          <Col xs={12} md={6} xl={7}>
            <span className="tagline">Welcome to my Portfolio</span>
            <h1>
              {`Hi I'm webdecoded `}
              <span className="wrap">{text}</span>
            </h1>
            <p>
              Lorem Ipsum is simply dummy text of the printing and typesetting
              industry. Lorem Ipsum has been the industry's standard dummy text
              ever since the 1500s, when an unknown printer took a galley of
              type and scrambled it to make a type specimen book.
            </p>
            <button
              onClick={() => {
                console.log('connect')
              }}>
              Let's connect
              <ArrowRightCircle size={25} />
            </button>
          </Col>
          <Col xs={12} md={6} xl={5}>
            <img src={headerImg} alt="Header Img" />
          </Col>
        </Row>
      </Container>
    </section>
  )
}

/src/App.js

import './App.css'
import 'bootstrap/dist/css/bootstrap.min.css'
import { NavBar } from './components/NavBar'
import { Banner } from './components/Banner'

function App() {
  return (
    <div className="App">
      <NavBar />
      <Banner />
    </div>
  )
}

export default App

Skills Section & Slider

npm install react-multi-carousel --save

/src/components/Skill.js

import { Col, Container, Row } from 'react-bootstrap'
import Carousel from 'react-multi-carousel'
import 'react-multi-carousel/lib/styles.css'

// images
import meter1 from '../assets/img/meter1.svg'
import meter2 from '../assets/img/meter2.svg'
import meter3 from '../assets/img/meter3.svg'
import colorSharp from '../assets/img/color-sharp.png'

export const Skills = () => {
  const responsive = {
    superLargeDesktop: {
      // the naming can be any, depends on you.
      breakpoint: { max: 4000, min: 3000 },
      items: 5,
    },
    desktop: {
      breakpoint: { max: 3000, min: 1024 },
      items: 3,
    },
    tablet: {
      breakpoint: { max: 1024, min: 464 },
      items: 2,
    },
    mobile: {
      breakpoint: { max: 464, min: 0 },
      items: 1,
    },
  }

  return (
    <section className="skill" id="skills">
      <Container>
        <Row>
          <Col>
            <div className="skill-bx">
              <h2>Skills</h2>
              <p>
                Lorem Ipsum is simply dummy text of the printing and typesetting
                industry.<br></br> Lorem Ipsum has been the industry's standard dummy
                text.
              </p>
              <Carousel
                responsive={responsive}
                infinite={true}
                className="skill-slider">
                <div className="item">
                  <img src={meter1} alt="Image" />
                  <h5>Web Development</h5>
                </div>
                <div className="item">
                  <img src={meter2} alt="Image" />
                  <h5>Brand Identity</h5>
                </div>
                <div className="item">
                  <img src={meter3} alt="Image" />
                  <h5>Logo Design</h5>
                </div>
                <div className="item">
                  <img src={meter1} alt="Image" />
                  <h5>Web Development</h5>
                </div>
              </Carousel>
            </div>
          </Col>
        </Row>
      </Container>

      <img className="background-image-left" src={colorSharp} />
    </section>
  )
}

/src/App.js

import './App.css'
import 'bootstrap/dist/css/bootstrap.min.css'
import { NavBar } from './components/NavBar'
import { Banner } from './components/Banner'
import { Skills } from './components/Skill'

function App() {
  return (
    <div className="App">
      <NavBar />
      <Banner />
      <Skills />
    </div>
  )
}

export default App

Projects Section & Tabs

/src/components/Projects.js

import { Col, Container, Row, Nav, Tab } from 'react-bootstrap'
import projImg1 from '../assets/img/project-img1.png'
import projImg2 from '../assets/img/project-img2.png'
import projImg3 from '../assets/img/project-img3.png'
import colorSharp2 from '../assets/img/color-sharp2.png'

import { ProjectCard } from './ProjectCard'

export const Projects = () => {
  const projects = [
    {
      title: 'Business Startup',
      description: 'Design & Development',
      imgUrl: projImg1,
    },
    {
      title: 'Business Startup',
      description: 'Design & Development',
      imgUrl: projImg2,
    },
    {
      title: 'Business Startup',
      description: 'Design & Development',
      imgUrl: projImg3,
    },
    {
      title: 'Business Startup',
      description: 'Design & Development',
      imgUrl: projImg1,
    },
    {
      title: 'Business Startup',
      description: 'Design & Development',
      imgUrl: projImg2,
    },
    {
      title: 'Business Startup',
      description: 'Design & Development',
      imgUrl: projImg3,
    },
  ]

  return (
    <section className="project" id="project">
      <Container>
        <Row>
          <Col>
            <h2>Projects</h2>
            <p>
              Lorem Ipsum is simply dummy text of the printing and typesetting
              industry. Lorem Ipsum has been the industry's standard dummy text
              ever since the 1500s, when an unknown printer took a galley of
              type and scrambled it to make a type specimen book.
            </p>

            <Tab.Container id="project-tabs" defaultActiveKey="first">
              {/* 导航栏 */}
              <Nav
                variant="pills"
                className="nav-pills mb-5 justify-content-center align-items-center"
                id="pills-tab">
                <Nav.Item>
                  <Nav.Link eventKey="first">Tab One</Nav.Link>
                </Nav.Item>
                <Nav.Item>
                  <Nav.Link eventKey="second">Tab Two</Nav.Link>
                </Nav.Item>
                <Nav.Item>
                  <Nav.Link eventKey="third">Tab Three</Nav.Link>
                </Nav.Item>
              </Nav>
              {/* 内容区域 */}
              <Tab.Content>
                <Tab.Pane eventKey="first">
                  <Row>
                    {projects.map((project, index) => {
                      return <ProjectCard key={index} {...project} />
                    })}
                  </Row>
                </Tab.Pane>
                <Tab.Pane eventKey="second">Loren Ipsum</Tab.Pane>
                <Tab.Pane eventKey="third">Loren Ipsum</Tab.Pane>
              </Tab.Content>
            </Tab.Container>
          </Col>
        </Row>
      </Container>

      <img className="background-image-right" src={colorSharp2} alt="" />
    </section>
  )
}

/src/components/ProjectCard.js

import { Col } from 'react-bootstrap'

export const ProjectCard = ({ title, description, imgUrl }) => {
  return (
    <Col sm={6} md={4}>
      <div className="proj-imgbx">
        {/* 默认显示图片 */}
        <img src={imgUrl} />
        {/* hover遮罩, css控制 */}
        <div className="proj-txtx">
          <h4>{title}</h4>
          <span>{description}</span>
        </div>
      </div>
    </Col>
  )
}

/src/components/App.js

import './App.css'
import 'bootstrap/dist/css/bootstrap.min.css'
import { NavBar } from './components/NavBar'
import { Banner } from './components/Banner'
import { Skills } from './components/Skill'
import { Projects } from './components/Projects'

function App() {
  return (
    <div className="App">
      <NavBar />
      <Banner />
      <Skills />
      <Projects />
    </div>
  )
}

export default App

Contact

/src/components/Contact.js

import { useState } from 'react'
import { Col, Container, Row } from 'react-bootstrap'
import contactImg from '../assets/img/contact-img.svg'

export const Contact = () => {
  const formInitialDetails = {
    firstName: '',
    lastName: '',
    email: '',
    phone: '',
    message: '',
  }

  const [formDetails, setFormDetails] = useState(formInitialDetails)
  const [buttonText, setButtonText] = useState('Send')
  const [status, setStatus] = useState({})

  const onFormUpdate = (category, value) => {
    setFormDetails({
      ...formDetails,
      [category]: value,
    })
  }

  const handleSubmit = async (e) => {
    e.preventDefault()
    setButtonText('Sending...')
    let response = await fetch('http://localhost:5000/contact', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json;charset=utf-8',
      },
      body: JSON.stringify(formDetails),
    })

    setButtonText('Send')
    let result = response.json()
    setFormDetails(formInitialDetails)
    if (result.code === 200) {
      setStatus({ success: true, message: 'Message sent successfully' })
    } else {
      setStatus({
        success: false,
        message: 'Something went wrong, please try again later.',
      })
    }
  }

  return (
    <section className="contact" id="connect">
      <Container>
        <Row className="align-items-center">
          <Col md={6}>
            <img src={contactImg} alt="Contact Us" />
          </Col>
          <Col md={6}>
            <h2>Get In Touch</h2>
            <form onSubmit={handleSubmit}>
              <Row>
                <Col sm={6} className="px-1">
                  <input
                    type="text"
                    value={formDetails.firstName}
                    placeholder="First Name"
                    onChange={(e) => {
                      onFormUpdate('firstName', e.target.value)
                    }}
                  />
                </Col>
                <Col sm={6} className="px-1">
                  <input
                    type="text"
                    value={formDetails.lastName}
                    placeholder="Last Name"
                    onChange={(e) => {
                      onFormUpdate('lastName', e.target.value)
                    }}
                  />
                </Col>
                <Col sm={6} className="px-1">
                  <input
                    type="email"
                    value={formDetails.email}
                    placeholder="Email"
                    onChange={(e) => {
                      onFormUpdate('lastName', e.target.value)
                    }}
                  />
                </Col>
                <Col sm={6} className="px-1">
                  <input
                    type="tel"
                    value={formDetails.phone}
                    placeholder="Phone No."
                    onChange={(e) => {
                      onFormUpdate('phone', e.target.value)
                    }}
                  />
                </Col>
                <Col>
                  <textarea
                    row="6"
                    value={formDetails.message}
                    placeholder="Message"
                    onChange={(e) => {
                      onFormUpdate('message', e.target.value)
                    }}></textarea>
                  <button type="submit">
                    <span>{buttonText}</span>
                  </button>
                </Col>

                {status.message && (
                  <Col>
                    <p
                      className={
                        status.success === false ? 'danger' : 'success'
                      }>
                      {status.message}
                    </p>
                  </Col>
                )}
              </Row>
            </form>
          </Col>
        </Row>
      </Container>
    </section>
  )
}
  • 服务器到时候再写

加动画

npm install animate.css --save
npm install --save react-on-screen

/src/components/Banner.js

import { useState, useEffect } from 'react'
import { Container, Row, Col } from 'react-bootstrap'
import { ArrowRightCircle } from 'react-bootstrap-icons'
import headerImg from '../assets/img/header-img.svg'

// 1.导入动画库 和 判断react组件是否出现在屏幕中 的库
import 'animate.css'
import TrackVisibility from 'react-on-screen'

export const Banner = () => {
  // 循环次数, 文字数组中第一条、第二条、第三条、第四条 4 % toRotate.length = 1
  const [loopNum, setLoopNum] = useState(0)
  // 当前语句是否打完, 如果打完了从后往前一个一个字符删除
  const [isDeleting, setIsDeleting] = useState(false)
  const toRotate = ['Web Developer', 'Web Designer', 'UI/UX Designer']
  const [text, setText] = useState('') // 当前显示的文字
  const [delta, setDelta] = useState(300 - Math.random() * 100)
  const period = 2000

  // 文字变化就执行
  useEffect(() => {
    let ticker = setInterval(() => {
      tick()
    }, delta)

    return () => {
      clearInterval(ticker)
    }
  }, [text])

  const tick = () => {
    // 第一遍是0
    let i = loopNum % toRotate.length
    let fullText = toRotate[i] // 当前打字动画播放完后, 对应完整的文字
    // 当前动画中的文字
    let updatedText = isDeleting
      ? fullText.substring(0, text.length - 1)
      : fullText.substring(0, text.length + 1)

    // 设置字符
    setText(updatedText)

    // 如果开始删除, 每次删除字符的延迟时间折半(参数默认为上一次的数值),就是越删越快
    if (isDeleting) {
      setDelta((prevDelta) => prevDelta / 2)
    }

    // 如果isDeleting为false 且 当前的文字打完了
    if (!isDeleting && updatedText === fullText) {
      // 开始删除
      setIsDeleting(true)
      setDelta(period) // 初始化是2秒删一个, 不过在上面是越来越快的
    } else if (isDeleting && updatedText === '') {
      // 删完了
      setIsDeleting(false)
      // 播放下一个
      setLoopNum(loopNum + 1)
      setDelta(500) // 设置从第二个开始0.5s打一次字了
    }
  }

  return (
    <section className="banner" id="home">
      <Container>
        <Row className="align-items-center">
          <Col xs={12} md={6} xl={7}>
            {/* 2.包裹, 元素当前是否可见 */}
            <TrackVisibility>
              {({ isVisible }) => (
                // 动画类
                <div
                  className={
                    isVisible ? 'animate__animated animate__fadeIn' : ''
                  }>
                  <span className="tagline">Welcome to my Portfolio</span>
                  <h1>
                    {`Hi I'm webdecoded `}
                    <span className="wrap">{text}</span>
                  </h1>
                  <p>
                    Lorem Ipsum is simply dummy text of the printing and
                    typesetting industry. Lorem Ipsum has been the industry's
                    standard dummy text ever since the 1500s, when an unknown
                    printer took a galley of type and scrambled it to make a
                    type specimen book.
                  </p>
                  <button
                    onClick={() => {
                      console.log('connect')
                    }}>
                    Let's connect
                    <ArrowRightCircle size={25} />
                  </button>
                </div>
              )}
            </TrackVisibility>
          </Col>
          <Col xs={12} md={6} xl={5}>
            <TrackVisibility>
              {({ isVisible }) => (
                <div
                  className={
                    isVisible ? 'animate__animated animate__zoomIn' : ''
                  }>
                  <img src={headerImg} alt="Header Img" />
                </div>
              )}
            </TrackVisibility>
          </Col>
        </Row>
      </Container>
    </section>
  )
}

react-portfolio's People

Contributors

zhang666zyh avatar

Stargazers

 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.