Node App:如何创建 Netflix 克隆版。使用 HTML、CSS、JS 进行 Netflix 克隆。
视频教程
代码
您可能觉得有用的文章
AWS GenAI 直播!
大家好,今天我们将学习如何仅使用 HTML、CSS 和 JS 轻松创建 Netflix 克隆版本,无需其他库。我们还将使用 TMDB API 从其数据库中获取真实数据。
Netflix Clone,我们日常生活中都使用Netflix。如果你刚开始接触Web开发,这个项目可以成为你一个很好的练习项目。这个Netflix Clone是一个动态网站,拥有你进行全栈开发实践所需的一切。它运行在Node.js服务器上,并使用TMDB API处理所有数据。
特征
- 看起来与 Netflix 相似。
- 动态站点在 Node.js 服务器上运行。
- 所有数据均来自 TMDB API。
- 专用动态电影信息页面。
- 有电影预告片和推荐。
- 具有流畅的卡片滑块效果。
如果您想观看演示或完整的编码教程视频,可以观看下面的教程。
视频教程
如果您能订阅我的 YouTube 频道来支持我,我将不胜感激。
因此,不要浪费更多时间,让我们看看如何编写代码。
代码
由于这是一个 node.js web 应用,我们需要 NPM 和 Node.js 才能启动,因此请确保您的系统中已安装它们。
那么让我们从它的文件夹结构开始吧。
文件夹结构。
这是我们的文件夹结构。
NPM 初始化
让我们从初始化 NPM 开始。在public文件夹外部,在你的root目录中,打开命令提示符或终端。然后执行。npm init
它会询问你一些详细信息。你可以按 Enter 键获取默认的项目详细信息。执行后,npm init你应该会看到一个package.json文件。
很好,现在安装创建服务器所需的一些库。
安装库
创建package.json文件后。运行此命令。
npm i express.js nodemon
i- 表示安装。express.js- 是我们创建服务器所需的库。nodemon- 是一个允许您即使在对服务器进行更改后也能无缝运行服务器的库。
安装完成后,您应该能够在目录node_modules中看到文件夹root。
现在package.json用文本编辑器打开文件,并进行一些编辑。
服务器.js
编辑后在目录中package.json创建 JS 文件。server.jsroot
并将其写入server.js。
const express = require('express');
const path = require('path');
let initial_path = path.join(__dirname, "public");
let app = express();
app.use(express.static(initial_path));
app.get('/', (req, res) => {
res.sendFile(path.join(initial_path, "index.html"));
})
app.listen(3000, () => {
console.log('listening on port 3000......');
})
解释
在顶部,我们使用require方法导入库,以便我们可以在此文件中使用它。我们导入了两个库express和path。
path库用于跟踪路径。
导入库完成后,我们将设置一个app等于 的变量express(),这样所有与服务器相关的功能都可以添加到我们的app变量中。此外,我们还设置了initial_path一个 ,用于保存public文件夹路径。
之后,我们有app.use()一个用作中间件的函数,它express.static()允许我们设置静态目录路径。在本例中,我们将public文件夹设置为静态路径,因为我们的HTML文件就在该文件夹中。
app.get('/')是一个监听器,在本例中,它监听的是指向GET我们根/路径的请求。每当我们收到任何GET请求时/,我们都会向其提供index.html文件。这就是我们res.sendFile()要做的。
我们的最后一块server.js是app.listen用于添加服务器监听端口的。在本例中,我们将其设置为3000。这样我们的服务器将在 上运行localhost:3000,而不是其他任何端口。
现在,在终端或 cmd 提示符下运行npm start以下命令启动服务器。然后,打开浏览器localhost:3000。您将能够看到index.html文件。
到目前为止,我们已经创建了服务器并成功地将index.html文件提供给/路径。
那么我们来做一些前端工作吧。现在
主页。
因此对于我们的主页,我们将使用这些文件。index.html,,,,。style.csshome.jsapi.jsscroll.js
让我们从index.html文件开始。先输入基本的 HTML 结构。然后是链接style.css文件。首先,我们来创建导航栏。
<!-- navbar -->
<nav class="navbar">
<img src="img/logo.png" class="logo" alt="">
<div class="join-box">
<p class="join-msg">unlimited tv shows & movies</p>
<button class="btn join-btn">join now</button>
<button class="btn">sign in</button>
</div>
</nav>
确保您的服务器正在运行,如果没有,则
npm start在您的终端中运行。
输出
我将用到的所有 CSS 属性都很容易理解。所以我只会解释 JS 部分。如果您对任何部分有任何疑问,即使是 CSS 部分,也欢迎在讨论中问我。
现在设置导航栏的样式
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
body{
width: 100%;
position: relative;
background: #181818;
font-family: 'roboto', sans-serif;
}
.navbar{
width: 100%;
height: 60px;
position: fixed;
top: 0;
z-index: 9;
background: #000;
padding: 0 2.5vw;
display: flex;
align-items: center;
}
.logo{
height: 60%;
}
.join-box{
width: fit-content;
display: flex;
justify-content: center;
align-items: center;
height: auto;
margin-left: auto;
}
.join-msg{
color: #fff;
text-transform: uppercase;
}
.btn{
border: 1px solid #fff;
border-radius: 2px;
background: none;
color: #fff;
height: 35px;
padding: 0 10px;
margin-left: 10px;
text-transform: uppercase;
cursor: pointer;
}
.join-btn{
background: #dd0e15;
border-color: #dd0e15;
}
输出
<!-- main section -->
<header class="main">
<h1 class="heading">movies</h1>
<p class="info">Movies move us like nothing else can, whether they're scary, funny, dramatic, romantic or anywhere in-between. So many titles, so much to experience.</p>
</header>
并设计它
.main{
position: relative;
margin-top: 60px;
width: 100%;
padding: 40px 2.5vw;
color: #fff;
}
.heading{
text-transform: capitalize;
font-weight: 900;
font-size: 50px;
}
.info{
width: 50%;
font-size: 20px;
margin-top: 10px;
}
我们必须在.main元素内部创建一个电影列表元素,它将保存相同类型的电影。
<div class="movie-list">
<button class="pre-btn"><img src="img/pre.png" alt=""></button>
<h1 class="movie-category">Popular movie</h1>
<div class="movie-container">
<div class="movie">
<img src="img/poster.jpg" alt="">
<p class="movie-title">movie name</p>
</div>
</div>
<button class="nxt-btn"><img src="img/nxt.png" alt=""></button>
</div>
你可以看到,我们创建了它们,pre-btn并且nxt-btn它们之间也包含一个movie-card元素。好吧,我们将使用 JS 创建电影卡片和列表元素,但出于样式方面的考虑,我们在这里只创建了一个卡片。这只是为了方便 CSS 使用。
.movie-list{
width: 100%;
height: 250px;
margin-top: 40px;
position: relative;
}
.movie-category{
font-size: 20px;
font-weight: 500;
margin-bottom: 20px;
text-transform: capitalize;
}
.movie-container{
width: 100%;
height: 200px;
display: flex;
align-items: center;
overflow-x: auto;
overflow-y: hidden;
scroll-behavior: smooth;
}
.movie-container::-webkit-scrollbar{
display: none;
}
.movie{
flex: 0 0 auto;
width: 24%;
height: 200px;
text-align: center;
margin-right: 10px;
cursor: pointer;
position: relative;
}
.movie img{
width: 100%;
height: 170px;
object-fit: cover;
}
.movie p{
text-transform: capitalize;
height: 20px;
overflow: hidden;
}
.pre-btn,
.nxt-btn{
position: absolute;
height: 200px;
top: 50%;
transform: translateY(-50%);
width: 2.5vw;
background: #181818;
border: none;
outline: none;
opacity: 0;
}
.pre-btn{
left: -2.5vw;
}
.nxt-btn{
right: -2.5vw;
}
.pre-btn img,
.nxt-btn img{
width: 20px;
height: 20px;
object-fit: contain;
}
.nxt-btn:hover,
.pre-btn:hover{
opacity: 1;
}
输出
一旦我们完成了卡片的造型,我们就可以提交它们。
<header class="main">
<h1 class="heading">movies</h1>
<p class="info">Movies move us like nothing else can, whether they're scary, funny, dramatic, romantic or anywhere in-between. So many titles, so much to experience.</p>
<!-- movie list -->
<!-- <div class="movie-list">
<button class="pre-btn"><img src="img/pre.png" alt=""></button>
<h1 class="movie-category">Popular movie</h1>
<div class="movie-container">
<div class="movie">
<img src="img/poster.jpg" alt="">
<p class="movie-title">movie name</p>
</div>
</div>
<button class="nxt-btn"><img src="img/nxt.png" alt=""></button>
</div> -->
</header>
我们的main版块应该看起来像这样。主页部分已经完成了。
现在将所有 JS 文件添加到index.html文件中。因为我们现在需要它们。
<script src="js/api.js"></script>
<script src="js/scroll.js"></script>
<script src="js/home.js"></script>
确保按照完全相同的顺序添加这些文件。
现在前往TMDB 官方网站创建 API 密钥。如果您不知道如何创建,请观看此视频。
创建 API 密钥后将其粘贴到api.js文件中
api.js
let api_key = "your api key";
然后前往TMDB 文档。找到以下三个 HTTP 链接。
api.js
let api_key = "your api key";
let img_url = "https://image.tmdb.org/t/p/w500";
let genres_list_http = "https://api.themoviedb.org/3/genre/movie/list?";
let movie_genres_http = "https://api.themoviedb.org/3/discover/movie?";
img_url- 用于获取图片。因为我们会获取电影图片的路径 ID。例如,如果我们获取的图片 ID 为,123则图片 URL 为https://image.tmdb.org/t/p/w500/123genres_list_http- 用于获取电影类型列表,这样我们就不必手动获取不同类型的电影。movie_genres_http- 是获取具有相同类型的电影。
完成这些 HTTPS 后,打开home.js文件。
home.js
fetch(genres_list_http + new URLSearchParams({
api_key: api_key
}))
.then(res => res.json())
.then(data => {
data.genres.forEach(item => {
fetchMoviesListByGenres(item.id, item.name);
})
});
解释
在这里,我们使用了在文件中声明fetch的方法。并用于向链接添加参数。获取 res 后,我们将其转换为 JSON ,转换为 JSON 后,我们得到了获取的数据。在理解我们在做什么之前,首先看一下获取的数据结构。genres_list_httpapi.jsnew URLSearchParamsapi_keyres.json()
这样就理解了数据结构。现在明白了获取 JSON 数据后我们在做什么。
data.genres.forEach(item => {
fetchMoviesListByGenres(item.id, item.name);
})
由于我们有一个包含各种类型的数组,因此我们使用方法循环遍历每个类型forEach。并在其中调用fetchMoviesListByGenres(id, genres)我们接下来要创建的方法。
现在获取具有不同类型的电影。
const fetchMoviesListByGenres = (id, genres) => {
fetch(movie_genres_http + new URLSearchParams({
api_key: api_key,
with_genres: id,
page: Math.floor(Math.random() * 3) + 1
}))
.then(res => res.json())
.then(data => {
makeCategoryElement(`${genres}_movies`, data.results);
})
.catch(err => console.log(err));
}
解释
在这里我们做同样的事情,我们正在获取数据,但在这种情况下,我们正在发出请求movie_genres_http并添加更多参数。paramwith_genres将为我们提供仅具有该类型的电影,例如,如果我们的类型 id 为喜剧电影,那么我们只会得到喜剧电影。parampage将提供我们想要的结果,在这种情况下,我们使用它Math.random()来获取一些随机的电影结果页面。
获取数据后,我们执行相同的res.json()操作将其转换为 JSON。调用makeCategoryElement(category, data)该函数将创建影片类别。同样,如果您愿意,也可以通过控制台记录数据结构。
现在创建电影类别。但在此之前,请main从 HTML 中选择我们的元素。
const main = document.querySelector('.main');
const makeCategoryElement = (category, data) => {
main.innerHTML += `
<div class="movie-list">
<button class="pre-btn"><img src="img/pre.png" alt=""></button>
<h1 class="movie-category">${category.split("_").join(" ")}</h1>
<div class="movie-container" id="${category}">
</div>
<button class="nxt-btn"><img src="img/nxt.png" alt=""></button>
</div>
`;
makeCards(category, data);
}
解释
在这个函数中,我们有两个参数,一个是category,另一个是data。所以我们函数做的第一件事就是使用 向我们的元素添加一个.movie-list元素。如果你还记得我们在 HTML 文件中创建的代码,但最后注释掉了,请复制并粘贴到这里。确保使用not ,因为我们不想重写它的 HTML。maininnerHTML+==
<h1 class="movie-category">${category.split("_").join(" ")}</h1>
如果你看到这行代码,首先,我们使用了 JS 模板字符串,如果不使用,你将无法像这样写。这里我们有一个h1元素,我们将它的文本设置为我们在函数开始时获取的类别。但我们也在这里执行了一些方法。让我们详细看看它们。
例如,假设类别等于喜剧。
<h1 class="movie-category">${category}</h1>那么输出将是 - comdey_movies。但我们不想,_这就是我们拆分它的原因。<h1 class="movie-category">${category.split("_")}</h1>那么它将无法工作,因为现在我们有一个数组 ["comedy", "movies"]。这就是为什么要使用join方法来连接数组。<h1 class="movie-category">${category.split("_").join(" ")}</h1>那么输出将是 - 喜剧电影
我希望你明白这一点。
然后我们为元素设置一个唯一的 ID,movie-container以便稍后添加卡片。最后,我们调用makeCards(category, data)该函数在电影容器元素内创建卡片。
现在创建一张卡片。
const makeCards = (id, data) => {
const movieContainer = document.getElementById(id);
data.forEach((item, i) => {
})
}
解释
在这个函数中,我们使用从上面函数获取的数据,在开始时选择影片容器元素id。之后,我们循环遍历datausingforEach方法。在方法内部,我们检查一些条件。
if(item.backdrop_path == null){
item.backdrop_path = item.poster_path;
if(item.backdrop_path == null){
return;
}
}
这个条件是检查,如果我们的结果集里没有电影backdrop图片路径,就把它设置poster_path为“否”。别搞砸了。有时 TMDB 电影的数据里没有图片路径,所以我们会检查它。
之后我们有
movieContainer.innerHTML += `
<div class="movie" onclick="location.href = '/${item.id}'">
<img src="${img_url}${item.backdrop_path}" alt="">
<p class="movie-title">${item.title}</p>
</div>
`;
这里,我们使用了该innerHTML方法来附加一开始就已经创建好的卡片 HTML 结构。同样,这里我们也使用了模板字符串。如果您看到我们有onclick一个事件到movie-card元素,我们将使用该事件location.href将用户重定向到我们接下来要创建的电影页面。
if(i == data.length - 1){
setTimeout(() => {
setupScrolling();
}, 100);
}
这是检查最后一次施法。卡片创建完成后,我们将运行setupScrolling()函数来设置滑块效果。我们也必须创建这个。
写完这么多 JS 代码,现在我们可以看到输出了,没有任何错误。
输出
但是我们还没有创建滑块效果写入。对于那个打开的scroll.js文件。
scroll.js
const setupScrolling = () => {
const conainter = [...document.querySelectorAll('.movie-container')];
const nxtBtn = [...document.querySelectorAll('.nxt-btn')];
const preBtn = [...document.querySelectorAll('.pre-btn')];
}
解释
querySelectorAll首先,在这个函数中,我们使用方法选择我们的容器、下一个按钮和上一个按钮。
选择它们后,在函数内部输入以下内容。
conainter.forEach((item, i) => {
let containerDimensions = item.getBoundingClientRect();
let containerWidth = containerDimensions.width;
})
这里我们循环遍历每个容器元素。并使用getBoundingClientRect方法获取容器的尺寸。最后将其存储containerDimensions.width(当然,这会给出容器的宽度)到containerWidth。
之后在这个 for 循环中添加这个。
nxtBtn[i].addEventListener('click', () => {
item.scrollLeft += containerWidth;
})
preBtn[i].addEventListener('click', () => {
item.scrollLeft -= containerWidth;
}
这里我们选择nxtBtn带有preBtn容器索引的元素,并为其添加点击事件。此外,还进行了一些简单的数学运算。
此后我们应该能够获得滑块效果。
我们的主页已经完成。
服务器.js
现在我们需要在关于页面中添加一些代码server.js。
在之前输入这些代码app.listen();
app.get('/:id', (req, res) => {
res.sendFile(path.join(initial_path, "about.html"));
})
app.use((req, res) => {
res.json("404");
})
这里,我们为路径添加了 GET 请求监听器/:id。这意味着任何前面带有单斜杠的路径都会执行代码。它适用于/123,但不适用于/123/12/1。最后,我们app.use()再次使用 which 作为中间件,这意味着如果请求路径与上述路径不同,则执行此操作。这意味着404消息。
此后,您将能够通过点击电影卡片将自己重定向到电影详细信息页面。
关于页面
让我们创建最后一个页面。为了避免编写大量的 CSS,我们可以使用链接about.css和文件。style.css
然后将导航栏复制粘贴到这里。之后创建 movie-info 元素
关于.html
<!-- movie info -->
<div class="movie-info">
<div class="movie-detail">
<h1 class="movie-name">Movie Name</h1>
<p class="genres">Comedy</p>
<p class="des">Lorem ipsum dolor sit amet consectetur, adipisicing elit. In commodi incidunt odit inventore suscipit, debitis officia modi exercitationem animi nemo.</p>
<p class="starring"><span>Starring:</span></p>
</div>
</div>
并进行造型。
.movie-info{
width: 100%;
height: calc(100vh - 60px);
margin-top: 60px;
background-size: cover;
background-repeat: no-repeat;
}
.movie-detail{
width: 50%;
height: 100%;
background: rgb(24, 24, 24);
background: linear-gradient(90deg, rgba(24, 24, 24, 1), rgba(24, 24, 24, 0) 100%);
padding: 5vw;
display: flex;
flex-direction: column;
justify-content: flex-end;
color: #fff;
}
.movie-name{
font-size: 30px;
font-weight: 500;
}
.genres{
opacity: 0.6;
margin: 30px 0;
}
.des{
width: 70%;
line-height: 20px;
margin-bottom: 30px;
}
.starring span{
opacity: 0.6;
}
输出
CSS 完成后,您可以从信息元素中删除所有文本,使其完全为空。
<h1 class="movie-name"></h1>
<p class="genres"></p>
<p class="des"></p>
<p class="starring"><span>Starring:</span></p>
像这样。
现在创建视频推荐。
<div class="trailer-container">
<h1 class="heading">Video Clip</h1>
<iframe src="" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
你应该注意到了iframe。这有点难理解,所以我建议你看一下这个视频预告片,以便更好地理解。Style
It。
.trailer-container,
.recommendations{
color: #fff;
padding: 5vw 5vw 0;
}
.heading{
font-size: 30px;
font-weight: 300;
margin-bottom: 20px;
}
iframe{
width: 400px;
height: 200px;
}
在输出中,除了电影信息元素和视频剪辑文本外,我们什么也看不到。因为我们的iframe源是空的。
现在创建推荐容器。
<div class="recommendations">
<h1 class="heading">More Like This</h1>
<div class="recommendations-container">
<div class="movie">
<img src="img/poster.jpg" alt="">
<p class="movie-title">movie name</p>
</div>
</div>
</div>
CSS
.recommendations-container{
width: 100%;
display: flex;
flex-wrap: wrap;
}
.movie p{
position: absolute;
bottom: 30px;
width: 100%;
height: 30px;
line-height: 30px;
background: rgba(0, 0, 0, 0.5);
text-align: center;
opacity: 0;
}
.movie:hover p{
opacity: 1;
}
输出
我们已经完成了样式设置。您可以对.movie元素进行注释。这与我们在主页中创建的元素相同。
也向此页面添加脚本。记住,添加顺序完全相同。
<script src="js/api.js"></script>
<script src="js/about.js"></script>
现在打开api.js文件。并添加以下内容。
let original_img_url = "https://image.tmdb.org/t/p/original";
let movie_detail_http = "https://api.themoviedb.org/3/movie";
您可以从 TMDB 文档中找到这些 HTTP。original_img_url- 这是为了获取原始分辨率的电影图像。movie_detail_http- 这是为了获取特定电影的详细信息。
现在打开about.js。然后写下这个。
let movie_id = location.pathname;
您location.pathname将能够从 URL 中提取电影 ID。例如,如果 URL 是,localhost:3000/123则这将返回/123我们的电影 ID。
之后使用相同的fetch方法获取电影详细信息,并将获取的数据传递给名为的函数setupMovieInfo(data)。
// fetching movie details
fetch(`${movie_detail_http}${movie_id}?` + new URLSearchParams({
api_key: api_key
}))
.then(res => res.json())
.then(data => {
setupMovieInfo(data);
})
让我们创造吧setupMovieInfo。
const setupMovieInfo = (data) => {
const movieName = document.querySelector('.movie-name');
const genres = document.querySelector('.genres');
const des = document.querySelector('.des');
const title = document.querySelector('title');
const backdrop = document.querySelector('.movie-info');
title.innerHTML = movieName.innerHTML = data.title;
genres.innerHTML = `${data.release_date.split('-')[0]} | `;
for(let i = 0; i < data.genres.length; i++){
genres.innerHTML += data.genres[i].name + formatString(i, data.genres.length);
}
if(data.adult == true){
genres.innerHTML += ' | +18';
}
if(data.backdrop_path == null){
data.backdrop_path = data.poster_path;
}
des.innerHTML = data.overview.substring(0, 200) + '...';
backdrop.style.backgroundImage = `url(${original_img_url}${data.backdrop_path})`;
}
解释
这个函数非常简单,首先它会选择所有元素,例如电影名称、标题标签、描述和类型。选择所有元素后,我们使用innerHTML方法设置值。但对于类型,我们有一些条件,例如,首先我们通过一些格式化操作只添加上映年份。之后,我们循环遍历电影数据中包含的所有类型,并将它们添加到类型中,并进行一些格式化。是的,您可以看到formatString函数,让我们来创建它。
const formatString = (currentIndex, maxIndex) => {
return (currentIndex == maxIndex - 1) ? '' : ', ';
}
backdrop_path类型之后,我们会像之前在主页上检查的那样进行检查。并将图片设置为背景图片。
由于我们没有获取电影详情中的演员信息,因此必须单独获取。
//fetching cast info
fetch(`${movie_detail_http}${movie_id}/credits?` + new URLSearchParams({
api_key: api_key
}))
.then(res => res.json())
.then(data => {
const cast = document.querySelector('.starring');
for(let i = 0; i < 5; i++){
cast.innerHTML += data.cast[i].name + formatString(i, 5);
}
})
我觉得这很容易理解。如果你有疑问,可以在讨论中问我。
现在如果我们看到输出。
输出
现在让我们获取视频片段。
/ fetching video clips
fetch(`${movie_detail_http}${movie_id}/videos?` + new URLSearchParams({
api_key: api_key
}))
.then(res => res.json())
.then(data => {
let trailerContainer = document.querySelector('.trailer-container');
let maxClips = (data.results.length > 4) ? 4 : data.results.length;
for(let i = 0; i < maxClips; i++){
trailerContainer.innerHTML += `
<iframe src="https://youtube.com/embed/${data.results[i].key}" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
`;
}
})
这里,我们获取与电影相关的视频详情。获取结果后,我们会检查要设置的条件maxClips,因为我们最多需要 4 个片段。之后,我们会循环maxClips播放。创建一个Iframe与 HTML 文件中相同的结构。将其从 HTML 文件中复制到 HTML 文件中。但请注意它的src属性。
输出
现在最后一件事是提出建议。
// fetch recommendations
fetch(`${movie_detail_http}${movie_id}/recommendations?` + new URLSearchParams({
api_key: api_key
}))
.then(res => res.json())
.then(data => {
let container = document.querySelector('.recommendations-container');
for(let i = 0; i < 16; i++){
if(data.results[i].backdrop_path == null){
i++;
}
container.innerHTML += `
<div class="movie" onclick="location.href = '/${data.results[i].id}'">
<img src="${img_url}${data.results[i].backdrop_path}" alt="">
<p class="movie-title">${data.results[i].title}</p>
</div>
`;
}
})
项目的最后一步,我们从 TMDB 中获取类似的电影。获取数据后,我们只制作了 16 张卡片。这与我们之前在 中创建卡片的方法非常相似home.js。
输出
我们完成了。
好了,就是这样。希望你理解了所有内容。如果你有疑问或者我遗漏了什么,请在评论区告诉我。
您可能觉得有用的文章
如果你能订阅我的YouTube频道,我将不胜感激。我创作了精彩的网络内容。订阅吧
感谢您的阅读。
文章来源:https://dev.to/themodernweb/how-to-create-netflix-clone-netflix-clone-with-hmtl-css-js-989
后端开发教程 - Java、Spring Boot 实战 - msg200.com


