How to make fully responsive modern portfolio using pure HTML, CSS and JS. Video Tutorial Code Articles you may find Useful

2025-05-28

如何使用纯 HTML、CSS 和 JS 制作完全响应的现代作品集。

视频教程

代码

您可能觉得有用的文章

大家好,今天我们将学习如何使用纯 HTML、CSS 和 JS 制作一个完全响应式的现代作品集,无需其他库。您将学习如何创建响应式设计,学习 CSS 伪元素。您将学习使用 Nodemailer 制作一个可用的联系表单,以及更多内容。

如果您想观看演示或需要带说明的编码教程,您可以观看下面的教程。

视频教程

如果您能订阅我的 YouTube 频道来支持我,我将不胜感激。

因此,不要浪费更多时间,让我们看看如何编写代码。

代码

在我们开始编写代码之前,让我们看看它的文件夹结构。

第 8 帧

是的,它是一个 nodeJS 应用程序,因为我们想要可用的邮件系统,并且只有服务器可以发送邮件,而不是客户端浏览器。

正如你所注意到的,我们有project.js一个文件。这些文件包含我们的项目数据。拥有项目数据可以让你更轻松地添加、删除或编辑任何你想要的项目。让我们看看它的数据结构。

let projects = [
    {
        name: "project one",
        tags: "#javascript, #fullstack, #ui/ux, #backend",
        image: "project (1).png",
    },
    {
        name: "project two",
        tags: "#javascript, #fullstack",
        image: "project (2).png",
    },
    // +8 more
]
Enter fullscreen mode Exit fullscreen mode

您可以看到我们有项目名称、标签和图像路径。这样我们就可以轻松处理项目,而无需编辑任何代码。

那么让我们从初始化 NPM 开始。

NPM 初始化

在根目录下的公共文件夹之外,打开命令提示符或终端并运行npm initcmd。这会将 NPM 初始化到你的项目中。

现在运行此命令来安装这些库。

npm i express.js nodemon nodemailer dotenv
Enter fullscreen mode Exit fullscreen mode

express.js- 用于创建服务器
nodemon- 用于持续运行服务器
nodemailer- 用于发送邮件
dotenv- 用于设置环境变量。我们将使用它来在服务器外部存储我们的电子邮件 ID 和密码。

安装完库之后,让我们在中进行一些更改package.json。打开它,并更改其scripts数据。

"scripts": {
    "start": "nodemon server.js"
  },
Enter fullscreen mode Exit fullscreen mode

然后server.js在根目录(而不是公共文件夹)中创建文件。打开它。

服务器.js

首先导入库/包。

const express = require('express');
const path = require('path');
const nodemailer = require('nodemailer');
const dotenv = require('dotenv');
Enter fullscreen mode Exit fullscreen mode

设置dotenv以便我们可以访问环境变量。

dotenv.config();
Enter fullscreen mode Exit fullscreen mode

然后将公共文件夹路径存储到变量并创建服务器。

let initialPath = path.join(__dirname, "public");
let app = express();
Enter fullscreen mode Exit fullscreen mode

现在使用app.use方法来设置中间件。

app.use(express.static(initialPath));
app.use(express.json());
Enter fullscreen mode Exit fullscreen mode

它们都很重要,express.json将启用表单数据共享并将express.static公共文件夹设置为静态路径。

之后创建主路线。并发送index.html文件。

app.get('/', (req, res) => {
    res.sendFile(path.join(initialPath, "index.html"));
})
Enter fullscreen mode Exit fullscreen mode

最后,让服务器监听3000端口。

app.listen(3000, () => {
    console.log('listening.....');
})
Enter fullscreen mode Exit fullscreen mode

好了,我们的服务器现在完成了。让我们npm start在终端上运行 cmd 来运行服务器。

现在让我们开始着手投资组合。

文件夹

打开index.html并编写基本的 HTML 结构。然后链接style.cssapp.js文件。之后创建导航栏。

<!-- navbar  -->
<nav class="navbar">
    <h1 class="brand">logo</h1>
    <div class="toggle-btn">
        <span></span>
        <span></span>
    </div>
    <ul class="links-container">
        <li class="links-item"><a href="#" class="link active">home</a></li>
        <li class="links-item"><a href="#project-section" class="link">project</a></li>
        <li class="links-item"><a href="#about-section" class="link">about</a></li>
        <li class="links-item"><a href="#contact-section" class="link">contact</a></li>
    </ul>
</nav>
Enter fullscreen mode Exit fullscreen mode

并赋予它一些风格。

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

html{
    scroll-behavior: smooth;
}

body{
    width: 100%;
    position: relative;
    background: #1d1d1d;
    color: #fff;
    font-family: 'roboto', sans-serif;
}

/* navbar */

.navbar{
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 60px;
    background: #1d1d1d;
    padding: 0 10vw;
    display: flex;
    justify-content: space-between;
    align-items: center;
    z-index: 9;
}

.brand{
    text-transform: capitalize;
    font-weight: 500;
}

.links-container{
    display: flex;
    list-style: none;
}

.link{
    text-transform: capitalize;
    color: #fff;
    text-decoration: none;
    margin: 0 10px;
    padding: 10px;
    position: relative;
}

.link:hover:not(.active){
    opacity: 0.7;
}

.link.active::before,
.seperator::before{
    content: '';
    position: absolute;
    bottom: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: #fff;
}

.link.active::after,
.seperator::after{
    content: '';
    position: absolute;
    bottom: 2px;
    left: 0;
    width: 100%;
    height: 1px;
    background: #fff;
}
Enter fullscreen mode Exit fullscreen mode

笔记

  1. 您可以看到scroll-behavior已赋予html。如果不赋予此项,则无法获得平滑滚动的效果。
  2. seperator您还可以在样式中看到元素,但不用担心,我们稍后会创建它。
输出

捕获

现在创建标题部分

<!-- home section -->
<section class="home">
    <div class="hero-content">
        <h1 class="hero-heading"><span class="highlight">hi, </span>i am john</h1>
        <p class="profession">web developer</p>
        <p class="info">Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur odit in laudantium suscipit blanditiis asperiores.</p>
        <a href="#contact-section" class="btn">contact</a>
    </div>
    <img src="img/img1.png" class="image" alt="">
</section>
Enter fullscreen mode Exit fullscreen mode
/* home section */

.home{
    width: 100%;
    min-height: calc(100vh - 60px);
    height: auto;
    margin-top: 60px;
    padding: 0 10vw;
    display: flex;
    align-items: center;
    justify-content: space-between;
    position: relative;
}

.hero-content{
    width: 50%;
}

.hero-heading{
    font-size: 5rem;
    text-transform: capitalize;
    font-weight: 500;
}

.highlight{
    color: #ff3559;
}

.profession{
    width: fit-content;
    display: block;
    margin: 10px 0 20px;
    margin-left: auto;
    text-transform: capitalize;
    position: relative;
    padding: 10px 20px;
    color: #1d1d1d;
    z-index: 2;
}

.profession::before{
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: #e3e3e3;
    z-index: -1;
    transform: skewX(10deg);
}

.profession::after{
    content: '';
    position: absolute;
    top: 0;
    left: -100px;
    width: 100px;
    height: 2px;
    background: #e3e3e3;
}

.info{
    line-height: 30px;
    margin-bottom: 50px;
}

.btn{
    padding: 10px 20px;
    text-decoration: none;
    border-radius: 50px;
    background: #ff3559;
    color: #fff;
    text-transform: capitalize;
    border: none;
}
Enter fullscreen mode Exit fullscreen mode
输出

捕获2

太棒了!现在制作关于部分。

<!-- about section -->
<section class="about" id="about-section">
    <h2 class="heading">about <span class="highlight">me</span></h2>
    <p class="sub-heading">Lorem ipsum dolor sit amet consectetur. </p>
    <div class="seperator"></div>

    <div class="about-me-container">
        <div class="left-col">
            <img src="img/img2.png" class="about-image" alt="">
        </div>
        <div class="right-col">
            <p class="about-para">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus totam quia numquam tempora nostrum earum similique enim laudantium iusto. Quaerat illo numquam minus pariatur, cum qui ipsum sapiente, atque optio voluptatibus necessitatibus, quis dolores veniam delectus inventore beatae? Accusamus, illum! Non nam dolores assumenda quibusdam repellat beatae quae eum atque sed, velit culpa, at animi cumque suscipit. Ratione delectus dolores odit dicta ipsum libero molestiae et reprehenderit sapiente earum. Alias aut architecto quis, earum iusto beatae quibusdam maiores, rerum, consequatur aliquid doloribus? Quas accusantium quidem eos ex, aperiam recusandae. Veritatis?</p>
            <a href="#" class="btn">download cv</a>
        </div>
    </div>
</section>
Enter fullscreen mode Exit fullscreen mode
/* about section */

.about{
    width: 100%;
    height: auto;
    padding: 50px 10vw;
}

.heading{
    text-align: center;
    font-weight: 500;
    font-size: 3.5rem;
    text-transform: capitalize;
}

.sub-heading{
    text-align: center;
    font-size: 1rem;
    margin: 10px;
    opacity: 0.7;
}

.seperator{
    width: 25%;
    margin: 20px auto;
    position: relative;
}

.about-me-container{
    margin: 150px 0 100px;
    width: 100%;
    display: grid;
    grid-template-columns: 40% 60%;
    grid-gap: 50px;
}

.left-col, .right-col{
    position: relative;
}

.left-col::before{
    content: 'yes, its me';
    text-transform: capitalize;
    position: absolute;
    right: 0;
    top: -20px;
}

.left-col::after{
    content: '';
    position: absolute;
    top: -10px;
    right: 80px;
    width: 50px;
    height: 2px;
    background: #fff;
    transform-origin: right;
    transform: rotate(-30deg);
}

.about-image{
    border-radius: 10px;
    box-shadow: 0 10px 10px rgba(0, 0, 0, 0.25);
}

.about-para{
    font-size: 1.2rem;
    font-weight: 300;
    line-height: 35px;
    margin-bottom: 40px;
}
Enter fullscreen mode Exit fullscreen mode
输出

捕获3

现在创建技能部分。在about部分中添加此结构。

<section class="about" id="about-section">
      //previous elements
<h2 class="heading">languages and framework i know</h2>
    <div class="seperator"></div>
    <div class="skill-container">
        <div class="skill-card" style="--bg: #f06529">
            <p class="skill">HTML</p>
        </div>
        <div class="skill-card" style="--bg: #379ad6">
            <p class="skill">CSS</p>
        </div>
        <div class="skill-card" style="--bg: #cc6699">
            <p class="skill">SCSS</p>
        </div>
        <div class="skill-card" style="--bg: #f7df1e">
            <p class="skill">JavaScript</p>
        </div>
        <div class="skill-card large" style="--bg: #5ed9fb">
            <p class="skill">ReactJS</p>
        </div>
        <div class="skill-card large" style="--bg: #83cd29">
            <p class="skill">NodeJS</p>
        </div>
        <div class="skill-card" style="--bg: #326690">
            <p class="skill">Postgres SQL</p>
        </div>
        <div class="skill-card" style="--bg: #ffa000">
            <p class="skill">Firebase</p>
        </div>
        <div class="skill-card large" style="--bg: #5ed9fb">
            <p class="skill">Much More</p>
        </div>
    </div>
</section>
Enter fullscreen mode Exit fullscreen mode

你会注意到我们有style="--bg: value"一个skill-card元素。它的作用是--bg为不同的元素设置不同的 CSS 变量。这样我们就可以用不同的颜色添加相同的效果。

.skill-container{
    position: relative;
    margin-top: 100px;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-gap: 20px;
}

.skill-card{
    height: 200px;
    border-radius: 10px;
    border: 1px solid #464646;
    text-align: center;
    position: relative;
    cursor: pointer;
    transition: .5s;
}

.skill{
    font-size: 2rem;
    color: #464646;
    line-height: 200px;
}

.skill-card:hover{
    background: var(--bg);
}

.skill-card:hover .skill{
    color: #fff;
}

.skill-card.large{
    grid-column: 2 span;
}
Enter fullscreen mode Exit fullscreen mode
输出

Capture4

悬停时显示背景颜色

现在,让我们创建项目部分。首先在项目部分中创建过滤按钮。

<!-- project section -->
<section class="project" id="project-section">
    <h2 class="heading">Project<span class="highlight">s</span></h2>
    <p class="sub-heading">Lorem ipsum dolor sit amet consectetur. </p>
    <div class="seperator"></div>

    <div class="filters">
        <button class="filter-btn active" id="all">all</button>
        <button class="filter-btn" id="javascript">javaScript</button>
        <button class="filter-btn" id="ui">ui/ux</button>
        <button class="filter-btn" id="backend">backend</button>
        <button class="filter-btn" id="fullstack">fullStack</button>
    </div>
</section>
Enter fullscreen mode Exit fullscreen mode

过滤器中的属性id将帮助我们过滤项目。

/* project section */

.project, .contact{
    position: relative;
    padding: 50px 10vw;
}

.filters{
    width: fit-content;
    display: block;
    margin: 100px auto;
}

.filter-btn{
    padding: 10px 20px;
    border-radius: 5px;
    border: none;
    text-transform: capitalize;
    margin: 0 5px 10px;
    cursor: pointer;
}

.filter-btn.active{
    background: #ff3559;
    color: #fff;
}
Enter fullscreen mode Exit fullscreen mode
输出

Capture5

现在仅出于造型目的制作单个项目卡。

<div class="project-container">
    <div class="project-card">
        <img src="img/project (1).png" alt="">
        <div class="content">
            <h1 class="project-name">project one</h1>
            <span class="tags">#javascript</span>
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode
.project-container{
    width: 100%;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-gap: 20px;
}

.project-card{
    position: relative;
    cursor: pointer;
    display: block;
}

.project-card img{
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.project-card .content{
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.7);
    display: flex;
    justify-content: center;
    align-items: center;
    transition: .5s;
    text-transform: capitalize;
    opacity: 0;
}

.project-name{
    font-weight: 300;
    font-size: 2.5rem;
    text-align: center;
}

.tags{
    position: absolute;
    bottom: 20px;
    opacity: 0.6;
    width: 90%;
}

.project-card:hover .content{
    opacity: 1;
}

.project-card.hide{
    display: none;
}
Enter fullscreen mode Exit fullscreen mode
输出

Capture6

悬停时显示内容

现在您可以对项目卡进行评论。

<div class="project-container">
    <!-- <div class="project-card">
        <img src="img/project (1).png" alt="">
        <div class="content">
            <h1 class="project-name">project one</h1>
            <span class="tags">#javascript</span>
        </div>
    </div> -->
</div>
Enter fullscreen mode Exit fullscreen mode

让我们动态地创建项目卡片。但在此之前,请先在文件project.js之前添加您的文件app.js。否则您将无法访问项目数据。

<script src="project.js"></script>
<script src="app.js"></script>
Enter fullscreen mode Exit fullscreen mode

现在打开app.js。在制作项目卡片之前,让我们先创建链接切换活动类。代码如下。

// links

const links = document.querySelectorAll('.link');

links.forEach(link => {
    link.addEventListener('click', () => {
        links.forEach(ele => ele.classList.remove('active'));
        link.classList.add('active');
    })
})
Enter fullscreen mode Exit fullscreen mode

完成这些之后,我们就可以开始处理项目卡片了。所以,编写代码。

// creating dynamic project card

const projectContainer = document.querySelector('.project-container');

projects.forEach(project => {
    projectContainer.innerHTML += `
    <div class="project-card" data-tags="#all, ${project.tags}">
        <img src="img/${project.image}" alt="">
        <div class="content">
            <h1 class="project-name">${project.name}</h1>
            <span class="tags">${project.tags}</span>
        </div>
    </div>
    `;
})
Enter fullscreen mode Exit fullscreen mode

您可以看到我们只是选择项目容器,然后循环数据来制作卡片。

输出

Capture7

太棒了!现在让过滤按钮可以正常工作了。

// filters

const filters = document.querySelectorAll('.filter-btn');

filters.forEach(filterBtn => {
    filterBtn.addEventListener('click', () => {
        let id = filterBtn.getAttribute('id');
        let projectCards = document.querySelectorAll('.project-card');
        projectCards.forEach(card => {
            if(card.getAttribute('data-tags').includes(id)){
                card.classList.remove('hide');
            } else{
                card.classList.add('hide');
            }
        })

        filters.forEach(btn => btn.classList.remove('active'));
        filterBtn.classList.add('active');
    })
})
Enter fullscreen mode Exit fullscreen mode

在上面的代码中,我们只是向过滤按钮添加了点击事件并切换了一些元素类。

好了,我们的项目部分已经全部完成了。现在,开始制作联系表格。

<!-- contact form -->
<section class="contact" id="contact-section">
    <h2 class="heading">Contact<span class="highlight"> me</span></h2>
    <p class="sub-heading">Lorem ipsum dolor sit amet consectetur. </p>
    <div class="seperator"></div>

    <div class="contact-form">
        <div class="name">
            <input type="text" class="first-name" required placeholder="first name">
            <input type="text" class="last-name" required placeholder="last name">
        </div>
        <input type="email" required class="email" placeholder="email">
        <textarea class="message" placeholder="message" required></textarea>
        <button class="btn contact-btn">contact</button>
    </div>
</section>

<footer class="footer">made with love by modern web</footer>
Enter fullscreen mode Exit fullscreen mode
/* contact form */

.contact-form{
    width: 100%;
    margin-top: 100px;
    position: relative;
}

.contact-form input, .message{
    width: 100%;
    display: block;
    height: 50px;
    padding: 20px;
    border-radius: 5px;
    background: #000;
    color: #fff;
    border: none;
    outline: none;
    margin: 30px 0;
    text-transform: capitalize;
    resize: none;
}

.message{
    height: 200px;
}

.contact-form .name{
    display: flex;
    justify-content: space-between;
}

.name input{
    width: 49%;
    margin: 0;
}

.contact-form .btn{
    display: block;
    margin: auto;
    cursor: pointer;
}

/* footer */

.footer{
    width: 100%;
    height: 30px;
    text-align: center;
    background-color: #ff3559;
    text-transform: capitalize;
    line-height: 30px;
}
Enter fullscreen mode Exit fullscreen mode
输出

Capture8

现在,让我们在里面创建邮件路由server.js

app.post('/mail', (req, res) => {
    const { firstname, lastname, email, msg } = req.body;

    const transporter = nodemailer.createTransport({
        service: 'gmail',
        auth: {
            user: process.env.EMAIL,
            pass: process.env.PASSWORD
        }
    })

    const mailOptions = {
        from: 'sender email',
        to: 'receiver email',
        subject: 'Postfolio',
        text: `First name: ${firstname}, \nLast name: ${lastname}, \nEmail: ${email}, \nMessage: ${msg}`
    }

    transporter.sendMail(mailOptions, (err, result) => {
        if (err){
            console.log(err);
            res.json('opps! it seems like some error occured plz. try again.')
        } else{
            res.json('thanks for e-mailing me. I will reply to you within 2 working days');
        }
    })
})
Enter fullscreen mode Exit fullscreen mode

这是使用 nodemailer 发送邮件的方法。有一些事项需要注意。

  1. process.env.EMAIL&process.env.PASSWORD这个关键字允许你访问环境变量,但我们还没有为此创建任何变量。在根目录中创建一个文件.env。名称应该相同。打开它并输入以下内容。
EMAIL=your email
PASSWORD=your email's password
Enter fullscreen mode Exit fullscreen mode

所以现在如果你明白了,process.env就会访问这些变量。

  1. fromto参数。在上面的代码中,我没有输入我的邮箱地址,但为了使邮件功能正常工作,您必须提供邮箱地址作为参数。您可以为两者提供相同的邮箱地址。

至此,我们的服务器已全部完成。现在,让联系表单正常工作。

//contact form
const contactBtn = document.querySelector('.contact-btn');
const firstName = document.querySelector('.first-name');
const lastName = document.querySelector('.last-name');
const email = document.querySelector('.email');
const msg = document.querySelector('.message');

contactBtn.addEventListener('click', () => {
    if(firstName.value.length && lastName.value.length && email.value.length && msg.value.length){
        fetch('/mail', {
            method: 'post',
            headers: new Headers({'Content-Type': 'application/json'}),
            body: JSON.stringify({
                firstname: firstName.value,
                lastname: lastName.value,
                email: email.value,
                msg: msg.value,
            })
        })
        .then(res => res.json())
        .then(data => {
            alert(data);
        })
    }
})
Enter fullscreen mode Exit fullscreen mode

在上面的代码中,我只是选择所有表单输入并发出路由POST请求/mail

我们的联系表格也已填写完毕。

现在让这个网站具有响应能力。

响应式 - 平板电脑

/* tablet view */

@media (max-width: 996px){
    html{
        font-size: 14px;
    }
    /* toggle btn */
    .toggle-btn{
        position: absolute;
        width: 40px;
        height: 40px;
        right: 10vw;
        cursor: pointer;
    }
    .toggle-btn span{
        position: absolute;
        width: 100%;
        height: 2px;
        background: #fff;
        top: 30%;
        transition: .5s;
    }
    .toggle-btn span:nth-child(2){
        top: 70%;
    }
    .toggle-btn.active span:nth-child(1){
        top: 50%;
        transform: rotate(45deg);
    }
    .toggle-btn.active span:nth-child(2){
        top: 50%;
        transform: rotate(-45deg);
    }
    /* links */
    .links-container{
        position: absolute;
        top: 60px;
        background: #1d1d1d;
        width: 100%;
        left: 0;
        padding: 0 10vw;
        flex-direction: column;
        transition: .5s;
        opacity: 0;
        pointer-events: none;
    }
    .links-container.show{
        opacity: 1;
        pointer-events: all;
    }
    .link{
        margin-left: auto;
        text-align: center;
        display: block;
        height: 50px;
    }
    /* home section */
    .home{
        flex-direction: column-reverse;
        height: fit-content;
        padding-bottom: 50px;
    }
    .home .image{
        width: 250px;
        margin: 40px;
    }
    .hero-content{
        width: 70%;
        min-width: 350px;
        text-align: center;
    }
    .hero-heading{
        font-size: 4.5rem;
    }

    /* about-section */
    .about-me-container{
        grid-template-columns: 1fr;
    }
    .left-col{
        margin: auto;
        width: 50%;
        min-width: 320px;
    }
    .skill-container, .project-container{
        grid-template-columns: repeat(2, 1fr);
    }
    .skill-card{
        grid-column: 1 span !important;
    }
}
Enter fullscreen mode Exit fullscreen mode

你可以看到我们toggle-btn在这里设置了样式。但要让它发挥作用,你需要在里面编写代码app.js

//toggle button
const toggleBtn = document.querySelector('.toggle-btn');
const linkContainer = document.querySelector('.links-container');

toggleBtn.addEventListener('click', () => {
    toggleBtn.classList.toggle('active');
    linkContainer.classList.toggle('show');
})
Enter fullscreen mode Exit fullscreen mode

响应式 - 移动

/* mobile view */
@media (max-width: 500px){
    html{
        font-size: 12px;
    }
    p, .sub-heading, .about-para, .left-col::before, .tags{
        font-size: 1.4rem;
    }
    .about-image{
        width: 90%;
        margin: auto;
        display: block;
    }
    .skill-container, .project-container{
        grid-template-columns: 1fr;
    }
    .skill{
        font-size: 2.5rem;
    }
    .project-name{
        font-size: 3rem;
    }
    .name{
        flex-direction: column;
    }
    .name input{
        width: 100%;
    }
    .first-name{
        margin-bottom: 20px !important;
    }
}
Enter fullscreen mode Exit fullscreen mode

您可以观看视频教程中的响应部分以了解输出

就这样吧。希望你理解了所有内容。如果你有疑问或者我遗漏了什么,请在评论区告诉我。

您可能觉得有用的文章

  1. 最佳 CSS 效果
  2. 音乐播放器应用
  3. Disney+ 克隆版
  4. Youtube API - Youtube 克隆
  5. TMDB - Netflix 克隆

如果您能订阅我的YouTube频道,我将不胜感激。我创作了非常棒的网络内容。

感谢您的阅读。

文章来源:https://dev.to/themodernweb/how-to-make-complete-responsive-modern-portfolio-using-pure-html-css-and-js-1p65
PREV
2022 年掌握编程的 7 大平台
NEXT
2025 年十大开源 VueJS 管理模板🤩