gsap--《pink老师vivo官网实现》

html部分(图片和视频自行替换)

js 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>vivo官网</title>
  <link rel="stylesheet" href="./css/index.css">
</head>

<body>
  <!-- 第一屏 -->
  <section class="screen section1">
    <div class="kv-content">
      <img src="./img/kv.webp" alt="">
    </div>
  </section>


  <!-- 第二屏幕 -->
  <section class="screen section2">
    <div class="summary-content">
      <video src="./media/summary.mp4" class="summary"></video>
      <p class="text1">
        流动山海纹¹ <br>
        光影层叠,山海流淌。
      </p>
      <p class="text2">
        东方灵韵 山海情
      </p>
    </div>
  </section>

  <!-- 第三屏幕 -->

  <section class="screen section3">
    <div class="color-img">
      <img src="./img/color1.webp" alt="" class="color1">
      <img src="./img/color2.webp" alt="" class="color2">
      <img src="./img/color3.webp" alt="" class="color3">
      <img src="./img/color4.webp" alt="" class="color4">
    </div>
  </section>

  <!-- 第四屏动画 -->
  <section class="screen section4">
    <div class="parallel">
      <div class="page1">
        <p class="title">美学创作大师</p>
        <video src="./media/video1.mp4" class="video1" muted></video>
        <div class="info">
          重塑美一秒 <br>
          自在享受视频拍摄吧!理
          想身材相机直出,让每一
          刻的出镜更自由纯粹。
        </div>
      </div>
      <div class="page2">
        <video src="./media/video2.mp4" class="video2" muted></video>
        <div class="info">
          <h4>放手去拍,自动成片⁹</h4>
          <p>Vlog 拍摄配套电影级滤镜,实时更新,免费</p>
        </div>
      </div>
    </div>
  </section>

  
  <!-- 第五屏动画 -->

    <!-- 第五屏动画 -->

    <section class="screen section5">
      <div class="rom-content">
        <div class="rom-txt">
          512GB
        </div>
        <div class="rom-img">
          <img src="./img/1.webp" alt="" class="pic1">
          <img src="./img/2.webp" alt="" class="pic2">
          <img src="./img/3.webp" alt="" class="pic3">
          <img src="./img/4.webp" alt="" class="pic4">
          <img src="./img/5.webp" alt="" class="pic5">
          <img src="./img/6.webp" alt="" class="pic6">
          <img src="./img/7.webp" alt="" class="pic7">
        </div>
      </div>
    </section>
  



  <!-- js部分 -->

  <script src="./js/gsap.min.js"></script>
  <script src="./js/ScrollTrigger.min.js"></script>
  <script src="./js/index.js"></script>
</body>

</html>

css部分

js 复制代码
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  /* 计算方法  15 / (1920/100) */
  font-size: 0.78125vw !important;
  overflow-x: hidden;
}

body {
  /* height: 50000px; */
  background-color: #fafafa;
}

.screen {
  position: relative;
  width: 100%;
  height: 100vh;
}

.kv-content {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  height: 100vh;
}

.kv-content img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* 第二屏幕的样式 */

.summary-content {
  position: absolute;
  top: 0;
  left: 50%;
  width: 100%;
  height: 100vh;
  transform: translate(-50%, 0);
}

.summary-content video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.text1 {
  position: absolute;
  top: 60rem;
  left: 21rem;
  opacity: 0;
  font-size: 4rem;
  color: #fff;
}

.text2 {
  position: absolute;
  top: 29.266666666rem;
  left: 29.333333333rem;
  opacity: 0;
  font-size: 8rem;
  color: #fff;
}

/* 第三屏幕样式 */

.color-img {
  width: 49em;
  position: absolute;
  top: 12em;
  left: 40em;
}

.color1 {
  width: 100%;
  position: absolute;
  top: 0;
  left: 0;
  margin-left: 90em;
}

.color2 {
  width: 100%;
  position: absolute;
  top: 0;
  left: 0;
  margin-left: 100em;
  scale: 1.1;
}

.color3 {
  width: 100%;
  position: absolute;
  top: 0;
  left: 0;
  margin-left: 110em;
  scale: 1.2;
}

.color4 {
  width: 100%;
  position: absolute;
  top: 0;
  left: 0;
  margin-left: 120em;
  scale: 1.3;
}

/* 第四屏动画 */
.parallel {
  width: 100%;
  height: 100%;
  margin-top: 0;
  position: relative;
  overflow: hidden;
  z-index: 50;
  box-sizing: border-box;
}

.page1 {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  /* background-color: pink; */
}
.page2 {
  display: flex;
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  width: 128em;
  /* background-color: skyblue; */
}


.page1 .title {
  width: 100%;
  text-align: center;
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-image: -webkit-linear-gradient(300deg, #4acbff 40%, #93ebff 47%, #ffd178 65%);
  font-size: 8em;
  font-family: HanyiVar-vivo-hwid-65;
  color: #000000;
  position: absolute;
  top: 50%;
  left: 0;
  transform: translateY(-50%);
}

.page1 .video1 {
  width: 20.3em;
  position: absolute;
  left: 40em;
  top: 50%;
  transform: translateY(-50%);
}

.page1 .info {
  position: absolute;
  left: calc(50% + 7em);
  top: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  opacity: 0;
  font-size: 1em;
  font-family: HanyiVar-vivo-hwid-65;
  color: #272727;
}



.page2 .video2 {
  margin-left: 10em;
  width: 68em;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.page2 .info {
  padding-top: 45px;
  margin-left: 10em;

  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
}


/* 第五屏动画 */


.rom-content {
  width: 100%;
  height: 100%;
  position: relative;
}

.rom-txt {
  width: 100%;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  left: 0;
  font-size: 20em;
  text-align: center;
  font-family: HanyiVar-vivo-hwid-65;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-image: -webkit-linear-gradient(110deg, #58b7f4 35%, #9ee6fa 55%);
  background-size: 200% 100%;
  background-position: left center;
  
}

.rom-img {
  position: absolute;
  width: 127em;
  left: 0;
  top: calc(50% + 45px);
  transform: translateY(-50%);
}

.pic1,
.pic7 {
  width: 11.266666666em;
  position: absolute;
  bottom: 5.26em;
}

.pic2,
.pic6 {
  width: 13.066666666em;
  position: absolute;
  bottom: 3.17em;
}

.pic3,
.pic5 {
  width: 14.866666666em;
  position: absolute;
  bottom: 1.06em;
}

.pic4 {
  width: 18.75em;
  bottom: 0;
  margin-left: 54.125em;
}

.pic1,
.pic2,
.pic3 {
  left: 55.125em;
  z-index: -1;
}

.pic5,
.pic6,
.pic7 {
  right: 55.125em;
  z-index: -1;
}

.pic img {
  position: absolute;
}

js部分

js 复制代码
// 第一屏:图片缩放 + 视频展开
gsap.timeline({
  scrollTrigger: {
    trigger: ".section1",
    start: "top top",
    end: "+=1000",
    scrub: true
  }
})
.fromTo(".kv-content", { scale: 1 }, { scale: 0.5 })
.fromTo(".summary-content", { width: "50%", height: "50vh" }, { width: "100%", height: "100vh" }, "<");

// 第二屏:文字显隐 + 视频进度联动
gsap.timeline({
  scrollTrigger: {
    trigger: ".section2",
    start: "top top",
    end: "+=5000",
    scrub: true,
    pin: true,
    onUpdate: (self) => {
        // 播放视频
    const summary = document.querySelector('.summary')
    try {
      // 视频的播放进度随着滚动条变化
      // self.progress 整体进度  0~1 
      // summary.duration 视频的总时长
      // console.log(self.progress)
      summary.currentTime = self.progress * summary.duration
    }
    catch (e) {
      console.log(e)
    }
    }
  }
})
.to(".text1", { top: "20rem", opacity: 1 })
.to(".text1", { top: 0, opacity: 0 })
.to(".text2", { top: "27rem", opacity: 1 })
.to(".text2", { top: "24rem", opacity: 0 });

// 第三屏:色块流行动画(需保留独立 pin 固定)
ScrollTrigger.create({ trigger: ".section3", start: "top top", end: "+=1000", pin: true }); // 仅固定
gsap.timeline({
  scrollTrigger: {
    trigger: ".color-img",
    start: "top-=500 top",
    end: "+=3000",
    scrub: true
  }
})
.fromTo(".color1", { marginLeft: "90em", opacity: 0 }, { marginLeft: 0, opacity: 1 }, "<")
.fromTo(".color2", { marginLeft: "100em", scale: 1.3 }, { marginLeft: 0, scale: 1 }, "<")
.fromTo(".color3", { marginLeft: "110em", scale: 1.6 }, { marginLeft: 0, scale: 1 }, "<")
.fromTo(".color4", { marginLeft: "120em", scale: 1.9 }, { marginLeft: 0, scale: 1 }, "<")
.fromTo(".color1", { marginLeft: 0, opacity: 1 }, { marginLeft: "-120em", opacity: 1 }, ">")
.fromTo(".color2", { marginLeft: 0, scale: 1 }, { marginLeft: "-110em", scale: 1.3 }, "<")
.fromTo(".color3", { marginLeft: 0, scale: 1 }, { marginLeft: "-100em", scale: 1.6 }, "<")
.fromTo(".color4", { marginLeft: 0, scale: 1 }, { marginLeft: "-90em", scale: 1.9 }, "<");

// 第四屏:双页面切换 + 视频精准控制
ScrollTrigger.create({ trigger: ".section4", start: "top top", end: "+=3000", pin: true }); // 仅固定
gsap.timeline({
  scrollTrigger: {
    trigger: ".parallel",
    start: "top top",
    end: "+=3000",
    scrub: true
  }
})
.fromTo(".title", { opacity: 1 }, { opacity: 0 })
.fromTo(".video1", { marginTop: "100%" }, { 
  marginTop: 0,
  onStart: () => {
    const v = document.querySelector(".page1 .video1");
    if (v) { v.currentTime = 0; v.play(); }
  }
})
.fromTo(".info", { opacity: 0 }, { opacity: 1 })
.fromTo(".page1", { left: 0 }, { left: "-128em" }, ">")
.fromTo(".page2", { left: "128em" }, { 
  left: 0,
  onStart: () => {
    const v = document.querySelector(".page2 .video2");
    if (v) { v.currentTime = 0; v.play(); }
  }
}, "<");

// 第五屏:图片聚合动画
ScrollTrigger.create({ trigger: ".section5", start: "top top", end: "+=3000", pin: true }); // 仅固定
gsap.timeline({
  scrollTrigger: {
    trigger: ".rom-content",
    start: "top+=500 top",
    end: "+=2000",
    scrub: true
  }
})
.fromTo(".rom-txt", { opacity: 1, marginTop: 0 }, { opacity: 0, marginTop: "-7em" })
.fromTo(".pic4", { width: "18.75em" }, { width: "16.75em" })
.fromTo(".pic1", { left: "55.125em" }, { left: "14.3em" }, "<")
.fromTo(".pic7", { right: "55.125em" }, { right: "14.3em" }, "<")
.fromTo(".pic2", { left: "55.125em" }, { left: "26.1em" }, "<")
.fromTo(".pic6", { right: "55.125em" }, { right: "26.1em" }, "<")
.fromTo(".pic3", { left: "55.125em" }, { left: "39.8em" }, "<")
.fromTo(".pic5", { right: "55.125em" }, { right: "39.8em" }, "<");

这是一个非常典型且高质量的视差滚动案例,使用了业界顶尖的动画库 GSAP 及其插件 ScrollTrigger

一、 JavaScript 部分详细解析

这段 JS 代码主要负责监听页面滚动,并将滚动进度映射为元素的动画进度。

1. 第一屏:图片缩放 + 视频展开

代码作用: 当用户向下滚动时,大图缩小,同时底部的视频容器从屏幕中间一小块区域展开至全屏,制造一种"拉开帷幕"的视觉效果。

javascript 复制代码
// 创建一个 GSAP 时间轴,用于管理一连串的动画
gsap.timeline({
  // 配置滚动触发器
  scrollTrigger: {
    trigger: ".section1", // 触发动画的元素是第一屏
    start: "top top",     // 当 .section1 的顶部碰到视口的顶部时开始
    end: "+=1000",        // 动画持续滚动 1000px 的距离
    scrub: true           // 关键属性:将动画进度与滚动条进度绑定,滚动条拖动则动画倒退/前进
  }
})
// 动画1:图片缩小。从 scale: 1 到 scale: 0.5
.fromTo(".kv-content", { scale: 1 }, { scale: 0.5 })
// 动画2:视频容器展开。
// "<" 表示该动画与上一个动画同时开始(默认是等待上一个结束后才开始)
// 从宽50%高50vh 变为 宽100%高100vh(全屏)
.fromTo(".summary-content", { width: "50%", height: "50vh" }, { width: "100%", height: "100vh" }, "<");
2. 第二屏:文字显隐 + 视频进度联动

代码作用: 固定屏幕,随着滚动播放视频,并在特定时间点显示介绍文字。这是类似苹果官网的经典交互。

javascript 复制代码
gsap.timeline({
  scrollTrigger: {
    trigger: ".section2",
    start: "top top",
    end: "+=5000",      // 这里的滚动距离很长(5000px),意味着需要滚动很久,相当于把视频"拉长"了
    scrub: true,
    pin: true,          // 关键属性:钉住当前屏幕,直到动画结束。这创造了"一镜到底"的效果
    onUpdate: (self) => {
      // 滚动更新时的回调函数
      const summary = document.querySelector('.summary') // 获取 video 元素
      try {
        // 原理核心:利用滚动进度控制视频播放进度
        // self.progress 是一个 0 到 1 的值,代表当前动画完成了百分之多少
        // 视频当前时间 = 进度 * 总时长
        summary.currentTime = self.progress * summary.duration
      }
      catch (e) {
        console.log(e)
      }
    }
  }
})
// 文字动画逻辑:文字从下方移入,停留,然后向上移出消失
.to(".text1", { top: "20rem", opacity: 1 }) // 移入
.to(".text1", { top: 0, opacity: 0 })       // 移出
.to(".text2", { top: "27rem", opacity: 1 }) // 第二段文字移入
.to(".text2", { top: "24rem", opacity: 0 }); // 第二段文字移出
3. 第三屏:色块流行动画

代码作用: 展示多张色彩图片,它们从屏幕右侧飞入,汇聚展示,然后再次飞出屏幕左侧。

javascript 复制代码
// 仅仅用来固定屏幕,不做动画逻辑,为了让用户停留在这一屏看完动画
ScrollTrigger.create({ trigger: ".section3", start: "top top", end: "+=1000", pin: true });
gsap.timeline({
  scrollTrigger: {
    trigger: ".color-img",
    start: "top-=500 top", // 提前500px开始,为了让动画衔接更自然
    end: "+=3000",
    scrub: true
  }
})
// 入场动画:从右侧大距离移入
// "<" 符号表示所有图片同时开始移动,形成阵列感
.fromTo(".color1", { marginLeft: "90em", opacity: 0 }, { marginLeft: 0, opacity: 1 }, "<")
.fromTo(".color2", { marginLeft: "100em", scale: 1.3 }, { marginLeft: 0, scale: 1 }, "<")
// ... 省略部分类似代码
// 出场动画:向左侧飞出
// ">" 符号表示在上一个动画完成后执行,或者这里使用了 "<" 意味着同步开始?
// 代码中写的是 ">" 在第一个出场动画后,后续使用了 "<" 同步
.fromTo(".color1", { marginLeft: 0, opacity: 1 }, { marginLeft: "-120em", opacity: 1 }, ">")
.fromTo(".color2", { marginLeft: 0, scale: 1 }, { marginLeft: "-110em", scale: 1.3 }, "<");
// ... 省略部分类似代码
4. 第四屏:双页面切换 + 视频精准控制

代码作用: 类似幻灯片切换,第一页向左滑出,第二页从右侧滑入,并在切换时自动播放对应的视频。

javascript 复制代码
ScrollTrigger.create({ trigger: ".section4", start: "top top", end: "+=3000", pin: true });
gsap.timeline({
  scrollTrigger: {
    trigger: ".parallel",
    start: "top top",
    end: "+=3000",
    scrub: true
  }
})
.fromTo(".title", { opacity: 1 }, { opacity: 0 }) // 标题淡出
.fromTo(".video1", { marginTop: "100%" }, { 
  marginTop: 0,
  // onStart: 视频出现在屏幕时触发的回调
  onStart: () => {
    const v = document.querySelector(".page1 .video1");
    if (v) { v.currentTime = 0; v.play(); } // 重置并播放视频
  }
})
.fromTo(".info", { opacity: 0 }, { opacity: 1 })
// 页面滑动切换效果
.fromTo(".page1", { left: 0 }, { left: "-128em" }, ">") // 第一页移出视野
.fromTo(".page2", { left: "128em" }, { 
  left: 0, // 第二页移入视野
  onStart: () => {
    const v = document.querySelector(".page2 .video2");
    if (v) { v.currentTime = 0; v.play(); } // 播放第二个视频
  }
}, "<"); // "<" 确保 page1 和 page2 同时移动,形成无缝衔接
5. 第五屏:图片聚合动画

代码作用: 多张图片从散乱的位置向中心聚合,模拟"收纳"或"产品全家福"的展示效果。

javascript 复制代码
ScrollTrigger.create({ trigger: ".section5", start: "top top", end: "+=3000", pin: true });
gsap.timeline({
  scrollTrigger: {
    trigger: ".rom-content",
    start: "top+=500 top",
    end: "+=2000",
    scrub: true
  }
})
.fromTo(".rom-txt", { opacity: 1, marginTop: 0 }, { opacity: 0, marginTop: "-7em" }) // 文字上移消失
.fromTo(".pic4", { width: "18.75em" }, { width: "16.75em" }) // 中心图片微调
// 图片位置移动,这里的 left/right 值变化实现了聚合效果
.fromTo(".pic1", { left: "55.125em" }, { left: "14.3em" }, "<")
.fromTo(".pic7", { right: "55.125em" }, { right: "14.3em" }, "<")
// ... 其他图片同理,通过改变 left/right 值向中间靠拢

二、 HTML 结构解析

HTML 结构非常清晰,采用了语义化标签模块化布局

  1. 容器划分 (section) : 每个 <section> 对应一个全屏的功能模块。class="screen" 确保了每个模块最小高度为视口高度。
  2. 资源引用 :
    • <video>: 视频标签,注意 muted 属性(静音),因为现代浏览器策略不允许自动播放有声视频。
    • <img>: 图片资源。
  3. 层级关系 : 例如第四屏,使用了 .parallel 容器包裹 .page1.page2,这是为了实现水平滑动切换做铺垫,父容器需要有 overflow: hidden 和相对定位。

三、 CSS 样式解析

CSS 部分主要处理了布局基准和元素的初始状态。

  1. REM 适配方案 :

    css 复制代码
    html { font-size: 0.78125vw !important; }

    原理 : 这是一个经典的移动端/PC端适配方案。

    • 假设设计稿宽度为 1920px。
    • 100vw = 1920px
    • 如果我们希望 1rem = 15px(设计稿上的量度),那么 1rem = 15/1920 * 100vw ≈ 0.78125vw
    • 这样,当屏幕变小时,font-size 变小,所有用 rem 做单位的元素都会等比缩放,实现响应式。
  2. 绝对定位 :

    • 大量使用了 position: absolute。这是为了配合 GSAP 动画。因为 GSAP 经常需要控制 top, left, margin 等属性,绝对定位可以让元素脱离文档流,随意移动而不影响其他元素位置。
  3. 初始状态隐藏 :

    • 很多元素(如 .text1, .text2)初始 opacity: 0
    • GSAP 动画负责将它们显示出来。不要让 CSS 动画和 JS 动画冲突,初始状态交给 CSS 定义。

四、 核心原理与设计思路

1. 为什么要用 scrub: true

这是 ScrollTrigger 最核心的功能。普通的动画是播放完就结束了,而 scrub 将动画与滚动条绑定。

  • 作用: 用户向前滚,动画播放;向后滚,动画倒退。这赋予了用户对时间的控制权,极大地增强了交互感。
2. 为什么要用 pin: true

在长页面滚动中,如果内容不够长,用户滚动太快会错过动画。

  • 作用 : pin 将当前部分"钉"在屏幕上,强制用户继续滚动来"消耗"设定的 end 距离(如 +=5000),但页面看起来还停留在这一屏。这相当于延长了时间轴,让导演(开发者)有足够的时间展示细节。
3. 视频联动原理 (onUpdate)

视频播放通常是线性的(时间流逝)。要实现滚动控制视频,关键公式是: currentTime = progress * duration 这打破了视频原本的时间流速,让视频变成了一个可以被用户" scrubbing (擦洗)"的素材,而非被动播放的媒体。

五、 拓展知识点

位置参数 (<>) : * 在 GSAP Timeline 中,控制动画时机非常关键。 * 默认是排队执行(一个接一个)。 * < 表示"在上一个动画开始时开始"。 * > 表示"在上一个动画结束时开始"。 * 熟练使用这两个符号,可以轻松编排复杂的并行/串行动画。

这里为什么要用两个固定?

js 复制代码
ScrollTrigger.create({
trigger: ".section3",
start: "top top",
end: "+=1000",
pin: true 
}); // 仅固定
gsap.timeline({
scrollTrigger: {
trigger: ".color-img",
start: "top-=500 top",
end: "+=3000",
scrub: true 
}
}) 

实际上,这里并不是使用了"两个固定" ,而是将**"固定容器" "内部动画"这两个逻辑分离了。 让我详细拆解一下为什么要这么写,以及这段代码里隐藏的一个关键逻辑冲突**。

1. 为什么看起来像"写了两个"?

我们来看这两段代码的区别:

  • 代码块 A(负责固定):

    javascript 复制代码
    ScrollTrigger.create({ 
      trigger: ".section3", 
      start: "top top", 
      end: "+=1000", 
      pin: true 
    });
    • 作用 :它的唯一职责是把 .section3 这个大容器"钉"在屏幕上,不随着页面滚动。
    • 对象:外层容器。
  • 代码块 B(负责动画):

    javascript 复制代码
    gsap.timeline({
      scrollTrigger: {
        trigger: ".color-img", // 注意:触发源变了,是内部的图片容器
        start: "top-=500 top",
        end: "+=3000",
        scrub: true
        // 这里没有 pin: true
      }
    })
    • 作用:负责控制图片飞入飞出的动画逻辑。
    • 对象:内部元素。

2. 为什么要拆开写?(核心原理)

如果合并在一起写,通常会有冲突。这里拆开的主要原因是为了实现**"入场动画提前"**的效果。 请看时间轴的对比:

  • 固定的开始时间start: "top top"
    • 意思是:当 .section3 顶部碰到屏幕顶部时,开始固定。
  • 动画的开始时间start: "top-=500 top"
    • 意思是:当 .color-img 距离屏幕顶部还有 500px 的时候,动画就开始了。 如果不拆开写: 如果你把 pin: true 写在 gsap.timeline 里,那么固定和动画会同时开始。用户必须先把屏幕滚到完全固定位置,图片才开始动,这样会显得很死板。 拆开写的效果:
  1. 用户向下滚动。
  2. 当图片距离顶部还有 500px 时,动画先开始(图片开始飞入)。
  3. 用户继续滚动,当 section 顶部到达屏幕顶部时,容器被固定住(pin 生效)。
  4. 用户继续滚动,动画继续进行。 结论: 拆开是为了让动画的触发点比固定的触发点更早,实现一种"先动起来,再停住展示"的流畅衔接感。

总结

  1. 不是两个固定:实际上是一个固定+ 一个动画控制。
  2. 为什么要拆:为了分离"固定触发点"和"动画触发点",实现更灵活的入场效果(动画比固定更早开始)。
相关推荐
www_stdio1 小时前
全栈项目第五天:构建现代企业级 React 应用:从工程化到移动端实战的全链路指南
前端·react.js·typescript
my_styles1 小时前
window系统安装/配置Nginx
服务器·前端·spring boot·nginx
神奇的程序员2 小时前
不止高刷:明基 RD280UG 在编码场景下的表现如何
前端
Rabbit_QL2 小时前
【音频处理】从 AirPods 主动降噪到音频 Source Separation:同一个问题的两种工程解法
前端·人工智能·音视频
-孤存-2 小时前
Spring Bean作用域与生命周期全解析
java·开发语言·前端
QEasyCloud20222 小时前
WooCommerce 独立站系统集成技术方案
java·前端·数据库
小宋10212 小时前
从 Kafka 告警到前端实时可见:SSE 在故障诊断平台中的一次完整落地实践
java·前端·kafka
jerrywus2 小时前
告别手动调试!用 Flutter MCP 让 AI 直接操控你的 App
前端·claude·mcp
浮桥2 小时前
uniapp + h5实现悬浮活动按钮组件
前端·javascript·uni-app