乐于分享
好东西不私藏

H5源码复现 | 快手十三周年 – 你身体里住着怎样的少年

H5源码复现 | 快手十三周年 – 你身体里住着怎样的少年

先看效果

已关注

关注

重播 分享

文件目录一览

代码部分

server.js

const Koa = require('koa');const serve = require('koa-static');const fetch = require('node-fetch');const path = require('path');const app = new Koa();const CDN_BASE = 'https://cdn.timaworks.com/kuaishou13/images';// 代理中间件:/proxy/xxx → CDNapp.use(async (ctx, next) => {  if (!ctx.path.startsWith('/proxy/')) return next();  const filename = ctx.path.slice('/proxy/'.length);  const cdnUrl = `${CDN_BASE}/${filename}`;  console.log(`[proxy] ${filename}`);  const headers = {};  if (ctx.headers.range) headers['Range'] = ctx.headers.range;  try {    const res = await fetch(cdnUrl, { headers });    ctx.status = res.status;    ['content-type','content-length','content-range','accept-ranges',     'last-modified','etag','cache-control'].forEach(h => {      const v = res.headers.get(h);      if (v) ctx.set(h, v);    });    ctx.set('Access-Control-Allow-Origin''*');    ctx.body = res.body;  } catch (err) {    console.error(`[proxy error]`, err.message);    ctx.status = 502;    ctx.body = 'proxy error';  }});// 静态文件(index.html 等)app.use(serve(path.join(__dirname)));const PORT = 3300;app.listen(PORT() => {  console.log(`服务已启动: http://localhost:3300`);});

index.html

<!DOCTYPE html><htmllang="zh-CN"><head>  <metacharset="UTF-8">  <metaname="viewport"content="width=device-width, initial-scale=1.0, user-scalable=no">  <title>你身体里住着怎样的少年</title>  <linkrel="stylesheet"href="style.css"></head><body><divid="stage">  <!-- ① 加载页 -->  <divid="loading"class="slider active fade-in">    <imgclass="full-img"src="/proxy/loading-bg.jpg"alt="">    <imgid="loading-text-img"class="full-img"src="/proxy/loading-text.png"alt="">    <imgid="loading-bird"src="/proxy/loading-bird.gif"alt="">    <imgid="loading-bar-img"src="/proxy/loading-percentBar.png"alt="">    <divid="loading-percent">0%</div>  </div>  <!-- ② Intro 介绍页 -->  <divid="intro"class="slider">    <divclass="video-layer"id="intro-mv-layer">      <videoclass="full-video"id="intro-mv"playsinlinemutedpreload="auto">        <sourcesrc="/proxy/intro-mv.mp4"type="video/mp4">      </video>    </div>    <divclass="video-layer"id="intro-loop-layer">      <videoclass="full-video"id="intro-loop"playsinlinemutedlooppreload="auto">        <sourcesrc="/proxy/intro-loop.mp4"type="video/mp4">      </video>    </div>  </div>  <!-- ③ Q1 -->  <divid="q1"class="slider">    <divclass="video-layer"id="q1-mv-layer">      <videoclass="full-video"id="q1-mv"playsinlinemutedpreload="auto">        <sourcesrc="/proxy/q1-mv.mp4"type="video/mp4">      </video>    </div>    <divclass="video-layer"id="q1-loop-layer">      <videoclass="full-video"id="q1-loop"playsinlinemutedlooppreload="auto">        <sourcesrc="/proxy/q1-loop.mp4"type="video/mp4">      </video>    </div>    <!-- shadownBt:[w,h,x,y] / 750x1624 -->    <divclass="options-area"id="q1-options">      <buttonclass="option-btn"data-index="0"style="left:18.67%;top:35.59%;width:45.33%;height:5.30%"></button>      <buttonclass="option-btn"data-index="1"style="left:28.00%;top:45.14%;width:57.87%;height:5.85%"></button>      <buttonclass="option-btn"data-index="2"style="left:17.33%;top:54.43%;width:66.00%;height:6.47%"></button>      <buttonclass="option-btn"data-index="3"style="left:18.67%;top:66.38%;width:63.33%;height:6.65%"></button>    </div>  </div>  <!-- ④ Q2 -->  <divid="q2"class="slider">    <divclass="video-layer"id="q2-mv-layer">      <videoclass="full-video"id="q2-mv"playsinlinemutedpreload="auto">        <sourcesrc="/proxy/q2-mv.mp4"type="video/mp4">      </video>    </div>    <divclass="video-layer"id="q2-loop-layer">      <videoclass="full-video"id="q2-loop"playsinlinemutedlooppreload="auto">        <sourcesrc="/proxy/q2-loop.mp4"type="video/mp4">      </video>    </div>    <divclass="options-area"id="q2-options">      <buttonclass="option-btn"data-index="0"style="left:18.27%;top:23.21%;width:40.00%;height:12.62%"></button>      <buttonclass="option-btn"data-index="1"style="left:46.80%;top:37.59%;width:44.27%;height:12.87%"></button>      <buttonclass="option-btn"data-index="2"style="left:4.93%;top:53.26%;width:46.80%;height:14.59%"></button>      <buttonclass="option-btn"data-index="3"style="left:44.27%;top:71.43%;width:49.73%;height:14.17%"></button>    </div>  </div>  <!-- ⑤ Q3 -->  <divid="q3"class="slider">    <divclass="video-layer"id="q3-mv-layer">      <videoclass="full-video"id="q3-mv"playsinlinemutedpreload="auto">        <sourcesrc="/proxy/q3-mv.mp4"type="video/mp4">      </video>    </div>    <divclass="video-layer"id="q3-loop-layer">      <videoclass="full-video"id="q3-loop"playsinlinemutedlooppreload="auto">        <sourcesrc="/proxy/q3-loop.mp4"type="video/mp4">      </video>    </div>    <divclass="options-area"id="q3-options">      <buttonclass="option-btn"data-index="0"style="left:17.33%;top:24.14%;width:25.07%;height:19.46%"></button>      <buttonclass="option-btn"data-index="1"style="left:56.93%;top:30.48%;width:29.60%;height:16.01%"></button>      <buttonclass="option-btn"data-index="2"style="left:4.80%;top:50.74%;width:44.80%;height:16.20%"></button>      <buttonclass="option-btn"data-index="3"style="left:64.00%;top:51.60%;width:32.67%;height:18.54%"></button>    </div>  </div>  <!-- ⑥ Q4(音频选项,有重放按钮) -->  <divid="q4"class="slider">    <divclass="video-layer"id="q4-mv-layer">      <videoclass="full-video"id="q4-mv"playsinlinemutedpreload="auto">        <sourcesrc="/proxy/q4-mv.mp4"type="video/mp4">      </video>    </div>    <divclass="video-layer"id="q4-loop-layer">      <videoclass="full-video"id="q4-loop"playsinlinemutedlooppreload="auto">        <sourcesrc="/proxy/q4-loop.mp4"type="video/mp4">      </video>    </div>    <!-- Q4 选项 + 重放按钮交替:奇数行=选项,偶数行=重放 -->    <divclass="options-area"id="q4-options">      <buttonclass="option-btn"data-index="0"style="left:4.93%;top:24.57%;width:49.33%;height:4.31%"></button>      <buttonclass="option-btn"data-index="1"style="left:50.13%;top:31.04%;width:35.60%;height:3.88%"></button>      <buttonclass="option-btn"data-index="2"style="left:4.27%;top:35.90%;width:50.40%;height:6.04%"></button>      <buttonclass="option-btn"data-index="3"style="left:50.00%;top:43.41%;width:46.00%;height:4.80%"></button>    </div>    <!-- 重放按钮 -->    <buttonclass="replay-btn"data-audio="q4a"style="display:none;left:57.87%;top:24.45%;width:10.00%;height:5.05%"></button>    <buttonclass="replay-btn"data-audio="q4b"style="display:none;left:33.73%;top:30.00%;width:14.80%;height:4.93%"></button>    <buttonclass="replay-btn"data-audio="q4c"style="display:none;left:56.93%;top:35.59%;width:13.60%;height:6.59%"></button>    <buttonclass="replay-btn"data-audio="q4d"style="display:none;left:34.67%;top:42.80%;width:12.00%;height:5.54%"></button>  </div>  <!-- ⑦ Info 页(填昵称、选性别) -->  <divid="info"class="slider">    <divclass="video-layer"id="info-mv-layer">      <videoclass="full-video"id="info-mv"playsinlinemutedpreload="auto">        <sourcesrc="/proxy/info-mv.mp4"type="video/mp4">      </video>    </div>    <divid="info-form">      <inputid="nickname-input"type="text"maxlength="6"placeholder="">      <!-- 对勾图片,选中后显示 -->      <imgid="checked-male"class="checked-img"src="/proxy/info-selected.png"alt="">      <imgid="checked-female"class="checked-img"src="/proxy/info-selected.png"alt="">      <imgid="checked-all"class="checked-img visible"src="/proxy/info-selected.png"alt="">      <!-- 透明点击热区 -->      <buttonclass="gender-tap"id="tap-male"data-sex="male"></button>      <buttonclass="gender-tap"id="tap-female"data-sex="female"></button>      <buttonclass="gender-tap"id="tap-all"data-sex="all"></button>      <buttonid="submit-btn"></button>    </div>  </div>  <!-- ⑧ Poster 结果页 -->  <divid="poster"class="slider">    <divclass="video-layer"id="ending-mv-layer">      <videoclass="full-video"id="ending-mv"playsinlinemutedpreload="auto">        <sourcesrc="/proxy/ending-mv.mp4"type="video/mp4">      </video>    </div>    <divclass="video-layer"id="ending-loop-layer">      <videoclass="full-video"id="ending-loop"playsinlinemutedlooppreload="auto">        <sourcesrc="/proxy/ending-loop.mp4"type="video/mp4">      </video>    </div>    <imgid="poster-img"alt="海报">    <imgid="tag-img"alt="标签">    <divid="ctag-text"></div>    <divid="poster-nickname"></div>    <divid="save-tip">长按图片保存</div>  </div>  <!-- 页面切换遮罩 -->  <divid="transition-mask"></div>  <!-- 音乐按钮(始终在 stage 左上角) -->  <buttonid="music-btn"></button></div><scriptsrc="app.js"></script></body></html>

app.js

// ============================================================// 答案映射表(原始 app.js 中的 posterMap)// key: "Q1答案 Q2答案 Q3答案 Q4答案",value: 海报数组// ============================================================const POSTER_MAP = {  "Ti Se Fi Ne": [{id:1,sex:"male",x:691,y:790,cTag:"举起生活的热血铁男"},{id:2,sex:"female",x:691,y:85,cTag:"体能MAX的热汗少女"},{id:19,sex:"female",x:691,y:790,cTag:"人见人爱的诺贝尓可爱奖得主"}],  "Ti Si Fi Ne": [{id:3,sex:"male",x:691,y:85,cTag:"满脑袋英雄梦的国风少年郎"},{id:4,sex:"female",x:58,y:169,cTag:"铁打的花仙子"}],  "Ti Se Fi Ni": [{id:5,sex:"female",x:59,y:789,cTag:"在森林找宝藏的波妞"},{id:6,sex:"female",x:59,y:789,cTag:"都市里的田园禾伙人"},{id:7,sex:"male",x:691,y:85,cTag:"永远在启程的荒野骑士"}],  "Ti Si Fi Ni": [{id:8,sex:"female",x:58,y:169,cTag:"人间淡淡主理人"},{id:9,sex:"male",x:691,y:85,cTag:"随地起舞的野生梦想家"},{id:7,sex:"male",x:691,y:85,cTag:"永远在启程的荒野骑士"}],  "Ti Se Fe Ne": [{id:10,sex:"male",x:58,y:169,cTag:"邪魅娟狂的语录小伙"},{id:11,sex:"female",x:691,y:85,cTag:"体会人生百态的全能掌镜人"},{id:6,sex:"female",x:59,y:789,cTag:"都市里的田园禾伙人"}],  "Ti Si Fe Ne": [{id:12,sex:"female",x:58,y:169,cTag:"自带BGM的美少女壮士"},{id:13,sex:"female",x:58,y:169,cTag:"如夏花般绚烂的C位少女"},{id:14,sex:"male",x:691,y:85,cTag:"没有背景靠自己的努力小伙"},{id:5,sex:"female",x:59,y:789,cTag:"在森林找宝藏的波妞"}],  "Ti Se Fe Ni": [{id:15,sex:"female",x:58,y:169,cTag:"语速180迈的互联网嘴替"},{id:16,sex:"male",x:58,y:169,cTag:"精神状态遥遥领先的显眼包"},{id:16,sex:"female",x:58,y:169,cTag:"精神状态遥遥领先的显眼包"},{id:17,sex:"male",x:691,y:85,cTag:"全是鬼点子的搞怪戏精"},{id:25,sex:"male",x:691,y:85,cTag:"爱说实话的小话唠"}],  "Ti Si Fe Ni": [{id:18,sex:"male",x:58,y:169,cTag:"专治各种不舒服的顶牛妙手"},{id:19,sex:"female",x:691,y:790,cTag:"人见人爱的诺贝尓可爱奖得主"}],  "Te Se Fi Ne": [{id:20,sex:"male",x:691,y:790,cTag:"迎着朝阳全力奔跑的孤勇者"},{id:21,sex:"female",x:691,y:790,cTag:"掌勺走天涯的少女宅急便"},{id:22,sex:"male",x:691,y:85,cTag:"大锅杂烩世间好味的游侠"},{id:22,sex:"female",x:691,y:85,cTag:"大锅杂烩世间好味的游侠"},{id:34,sex:"male",x:691,y:85,cTag:"走花路的民间超模"}],  "Te Si Fi Ne": [{id:23,sex:"female",x:691,y:85,cTag:"洞察先机的生意捕手"},{id:24,sex:"male",x:59,y:789,cTag:"游乐人间的不受控NPC"},{id:28,sex:"male",x:691,y:790,cTag:"天马行空的民间爱迪生"},{id:21,sex:"female",x:691,y:790,cTag:"掌勺走天涯的少女宅急便"}],  "Te Se Fi Ni": [{id:25,sex:"male",x:691,y:85,cTag:"爱说实话的小话唠"},{id:26,sex:"female",x:691,y:85,cTag:"24K的纯神金小妹"}],  "Te Si Fi Ni": [{id:27,sex:"male",x:58,y:169,cTag:"玩的就是真实的挑战玩家"},{id:28,sex:"male",x:691,y:790,cTag:"天马行空的民间爱迪生"},{id:31,sex:"female",x:691,y:85,cTag:"一路打怪升级的爽文女主"},{id:13,sex:"female",x:58,y:169,cTag:"如夏花般绚烂的C位少女"}],  "Te Se Fe Ne": [{id:29,sex:"male",x:691,y:85,cTag:"随地大小聊的社交悍匪"},{id:29,sex:"female",x:691,y:85,cTag:"随地大小聊的社交悍匪"},{id:30,sex:"male",x:691,y:790,cTag:"行走人间的行为E术家"},{id:31,sex:"female",x:691,y:85,cTag:"一路打怪升级的爽文女主"},{id:26,sex:"female",x:691,y:85,cTag:"24K的纯神金小妹"}],  "Te Si Fe Ne": [{id:32,sex:"male",x:691,y:85,cTag:"一秒换头的时尚ICON"},{id:33,sex:"female",x:691,y:790,cTag:"麻袋当披风也能是人间高定"},{id:34,sex:"male",x:691,y:85,cTag:"走花路的民间超模"}],  "Te Se Fe Ni": [{id:35,sex:"female",x:58,y:169,cTag:"每一步都算数的摘星女主"},{id:36,sex:"female",x:58,y:169,cTag:"真诚而热烈的毛孩子"},{id:36,sex:"male",x:58,y:169,cTag:"真诚而热烈的毛孩子"},{id:37,sex:"male",x:691,y:790,cTag:"东北话十级表演艺术家"},{id:4,sex:"female",x:58,y:169,cTag:"铁打的花仙子"},{id:20,sex:"male",x:691,y:790,cTag:"迎着朝阳全力奔跑的孤勇者"}],  "Te Si Fe Ni": [{id:38,sex:"male",x:691,y:85,cTag:"民间采风的野生音乐人"},{id:39,sex:"female",x:58,y:169,cTag:"实力开麦的嘴强王者"},{id:30,sex:"male",x:691,y:790,cTag:"行走人间的行为E术家"}],};// 每题答案编码(index → 字符串)const ANSWER_KEYS = [  ["Te","Ti","Ti","Te"],  // Q1  ["Si","Se","Si","Se"],  // Q2  ["Fi","Fi","Fe","Fe"],  // Q3  ["Ne","Ne","Ni","Ni"],  // Q4];// 原始设计稿尺寸const DESIGN_W = 750DESIGN_H = 1624;// ============================================================// 状态// ============================================================let answers = [];       // 收集的答案字符串let userSex = null;     // "male" | "female" | nulllet bgm = null;         // Audio 对象// ============================================================// 工具// ============================================================// 切换 sliderfunction showSlider(id) {  document.querySelectorAll('.slider').forEach(el => el.classList.remove('active'));  document.getElementById(id).classList.add('active');}// iOS:intro-mv 开始播放后,对其余视频 play() 后 100ms 暂停以触发缓冲function preloadOtherVideos() {  document.querySelectorAll('video:not(#intro-mv)').forEach(v => {    v.play().then(() => setTimeout(() => v.pause(), 300)).catch(() => {});  });}// 播放 mv,结束后回调;onPlaying 在视频真正有画面时调用function playMV(videoId, onEnded, onPlaying) {  const video = document.getElementById(videoId);  const layer = document.getElementById(videoId + '-layer');  video.onplaying = () => { layer.classList.add('visible'); if (onPlaying) onPlaying(); };  video.onended = onEnded || null;  setTimeout(() => video.play().catch(err => {    console.warn('autoplay blocked, waiting for interaction:', err.message);    const retry = () => { document.removeEventListener('click', retry); video.play().catch(() => {}); };    document.addEventListener('click', retry, { oncetrue });  }), 50);}// 显示并播放 loop 视频function playLoop(videoId) {  const video = document.getElementById(videoId);  document.getElementById(videoId + '-layer').classList.add('visible');  video.play().catch(() => {});}// 暂停视频function pauseVideo(videoId) {  const v = document.getElementById(videoId);  if (v) v.pause();}// 显示选项热区function showOptions(id) {  document.getElementById(id).classList.add('visible');}// 设计稿坐标 → 当前舞台比例(用于海报昵称定位)function scaleX(x) { return (x / DESIGN_W) * 100 + '%'; }function scaleY(y) { return (y / DESIGN_H) * 100 + '%'; }// ============================================================// 背景音乐// ============================================================function initBGM() {  bgm = new Audio('/proxy/music.mp3');  bgm.loop = true;  bgm.volume = 0.6;  const btn = document.getElementById('music-btn');  btn.addEventListener('click'() => {    if (bgm.paused) { bgm.play(); btn.classList.remove('paused'); }    else { bgm.pause(); btn.classList.add('paused'); }  });}function playBGM() {  if (bgm && bgm.paused) bgm.play().catch(() => {});}// ============================================================// 页面流程// ============================================================function goIntro() {  transitionToSlider('intro'(onReady) => {    playBGM();    playMV('intro-mv'() => {      playLoop('intro-loop');      setTimeout(() => goQ(1), 2000);    }, () => {      onReady();      preloadOtherVideos();    });  });}// 新 slider 叠在旧 slider 上,等 onReady() 调用后再淡入,淡入完成后隐藏旧的function transitionToSlider(targetSliderId, callback) {  const prev = document.querySelector('.slider.active');  const next = document.getElementById(targetSliderId);  next.classList.add('active');  const onReady = () => {    requestAnimationFrame(() => requestAnimationFrame(() => {      next.classList.add('fade-in');      setTimeout(() => {        if (prev && prev !== next) prev.classList.remove('active''fade-in');      }, 400);    }));  };  if (callback) callback(onReady);}function goQ(n) {  const prevLoops = ['intro-loop''q1-loop''q2-loop''q3-loop'];  pauseVideo(prevLoops[n - 1]);  // 背景图 id 对应关系  transitionToSlider('q' + n, (hideMask) => {    playMV('q' + n + '-mv'() => {      playLoop('q' + n + '-loop');      showOptions('q' + n + '-options');      if (n === 4showQ4Replays();    }, hideMask);  });}// ============================================================// Q4 音频重放// ============================================================const q4Audios = {};function initQ4Audios() {  ['q4a','q4b','q4c','q4d'].forEach(k => {    q4Audios[k] = new Audio('/proxy/' + k + '.mp3');  });}function stopAllQ4() {  Object.values(q4Audios).forEach(a => { a.pause(); a.currentTime = 0; });}function showQ4Replays() {  document.querySelectorAll('.replay-btn').forEach(btn => {    btn.style.display = 'block';  });  // 自动播放第一个  stopAllQ4();  q4Audios['q4a'].play().catch(() => {});}// ============================================================// Info 页// ============================================================function goInfo() {  pauseVideo('q4-loop');  transitionToSlider('info'(hideMask) => {    playMV('info-mv'() => {      document.getElementById('info-form').classList.add('visible');    }, hideMask);  });}// 性别选择document.querySelectorAll('.gender-tap').forEach(btn => {  btn.addEventListener('click'() => {    document.querySelectorAll('.checked-img').forEach(img => img.classList.remove('visible'));    document.getElementById('checked-' + btn.dataset.sex).classList.add('visible');    userSex = btn.dataset.sex === 'all' ? null : btn.dataset.sex;  });});// 提交document.getElementById('submit-btn').addEventListener('click'() => {  const nickname = document.getElementById('nickname-input').value.trim();  if (!nickname) { document.getElementById('nickname-input').focus(); return; }  goPoster(nickname);});// ============================================================// Poster 页// ============================================================function goPoster(nickname) {  // 根据答案 + 性别选海报  const key = answers.join(' ');  let pool = POSTER_MAP[key] || POSTER_MAP[Object.keys(POSTER_MAP)[0]];  if (userSex === 'male') pool = pool.filter(p => p.sex === 'male');  else if (userSex === 'female') pool = pool.filter(p => p.sex === 'female');  if (!pool.length) pool = POSTER_MAP[key] || [];  const pick = pool[Math.floor(Math.random() * pool.length)];  // 设置海报图  const posterImg = document.getElementById('poster-img');  posterImg.src = '/proxy/posters/poster-' + pick.id + '.jpg';  posterImg.onload = () => posterImg.classList.add('visible');  // 设置标签图  const tagImg = document.getElementById('tag-img');  tagImg.src = '/proxy/tags/tag-' + pick.id + '.png';  // 设置文字标签  document.getElementById('ctag-text').textContent = pick.cTag;  // 设置昵称位置(设计稿坐标换算)  const nn = document.getElementById('poster-nickname');  nn.textContent = '@' + nickname;  nn.style.left = scaleX(pick.x);  nn.style.top = scaleY(pick.y + 140);  transitionToSlider('poster'(hideMask) => {    // 播放 ending-mv → ending-loop,然后依次显示元素    playMV('ending-mv'() => {      playLoop('ending-loop');      setTimeout(() => {        tagImg.classList.add('visible');        nn.classList.add('visible');      }, 500);      setTimeout(() => {        document.getElementById('ctag-text').classList.add('visible');      }, 1500);      setTimeout(() => {        document.getElementById('save-tip').classList.add('visible');      }, 2500);    }, hideMask);  });}// ============================================================// 事件绑定// ============================================================// Q1~Q3 选项[1,2,3].forEach(n => {  document.getElementById('q' + n + '-options').addEventListener('click'e => {    const btn = e.target.closest('.option-btn');    if (!btn) return;    const idx = parseInt(btn.dataset.index);    answers.push(ANSWER_KEYS[n-1][idx]);    document.getElementById('q' + n + '-options').classList.remove('visible');    goQ(n + 1);  });});// Q4 选项document.getElementById('q4-options').addEventListener('click'e => {  const btn = e.target.closest('.option-btn');  if (!btn) return;  const idx = parseInt(btn.dataset.index);  answers.push(ANSWER_KEYS[3][idx]);  stopAllQ4();  document.getElementById('q4-options').classList.remove('visible');  document.querySelectorAll('.replay-btn').forEach(b => b.style.display = 'none');  goInfo();});// Q4 重放按钮document.querySelectorAll('.replay-btn').forEach(btn => {  btn.addEventListener('click'e => {    e.stopPropagation();    stopAllQ4();    q4Audios[btn.dataset.audio].play().catch(() => {});  });});// ============================================================// 加载 + 启动// ============================================================function startLoading() {  const bar = document.getElementById('loading-bar-img');  const text = document.getElementById('loading-percent');  let pct = 0;  let done = false;  const update = (v) => {    pct = v;    bar.style.width = (18.4 * v / 100) + '%';    text.textContent = v + '%';  };  const assets = ['loading-bg.jpg','intro-bg.jpg','q1-bg.jpg','q2-bg.jpg','q3-bg.jpg'];  let loaded = 0;  assets.forEach(name => {    const img = new Image();    img.onload = img.onerror = () => update(Math.round(++loaded / assets.length * 70));    img.src = '/proxy/' + name;  });  setTimeout(() => {    const t = setInterval(() => {      if (pct >= 100) {        clearInterval(t);        done = true;        // 进度跑完,等待点击小鸟        document.getElementById('loading-bird').style.cursor = 'pointer';        return;      }      update(pct + 1);    }, 25);  }, 600);  document.getElementById('loading-bird').addEventListener('click'() => {    if (!done) return;    goIntro();  });}initBGM();initQ4Audios();startLoading();

 style.css

* { margin0padding0box-sizing: border-box; -webkit-tap-highlight-color: transparent; }body {  background#000;  margin0;  display: flex;  justify-content: center;  align-items: center;  height100vh;  overflow: hidden;}#stage {  position: relative;  /* 电脑:按高度撑满,保持比例,两侧留黑 */  height100vh;  widthcalc(100vh * 750 / 1334);  overflow: hidden;  background#000;  flex-shrink0;}/* 手机:完全撑满视口,不保持比例(原版行为) */@media (max-aspect-ratio750/1334) {  body { height100dvh; }  #stage {    width100vw;    height100dvh;  }}.slider {  position: absolute;  inset0;  display: none;  opacity0;  transition: opacity 0.4s;}.slider.active { display: block; }.slider.fade-in { opacity1; }/* 全屏视频/图片 */.full-video.full-img {  position: absolute;  inset0;  width100%;  height100%;  object-fit: cover;}/* 视频层(mv/loop 叠加,fade 切换) */.video-layer {  position: absolute;  inset0;  opacity0;  transition: opacity 0.3s;}.video-layer.visible { opacity1; }/* 加载页 */#loading-bird {  position: absolute;  left37.07%;  top35.47%;  width10%;}#loading-bar-img {  position: absolute;  left40.8%;        /* x=306/750 */  top47.66%;        /* y=774/1624 */  height1%;         /* 图片本身高度自适应 */  width0%;  max-width18.4%;   /* 138/750 */  overflow: hidden;  transition: width 0.3s;  object-fit: cover;  object-position: left;}#loading-percent {  position: absolute;  width100%;  text-align: center;  top61.6%;  color#fff;  font-size14px;}/* 透明选项热区 */.options-area {  position: absolute;  inset0;  display: none;  z-index10;}.options-area.visible { display: block; }.option-btn {  position: absolute;  cursor: pointer;  background: transparent;  border: none;  /* 调试坐标时取消注释:*/  /* outline: 2px solid rgba(255,0,0,0.5); */}.replay-btn {  position: absolute;  cursor: pointer;  background: transparent;  border: none;  z-index11;  /* outline: 2px solid rgba(0,255,0,0.5); */}/* ---- Info 页 ---- */#info-form {  position: absolute;  inset0;  z-index10;  display: none;  pointer-events: none;}#info-form.visible {  display: block;  pointer-events: auto;}/* 输入框:可见,覆盖在视频对应位置 *//* 原始坐标 x:240,y:760 / 750x1624 */#nickname-input {  position: absolute;  left32%;        /* 240/750 */  top46.8%;       /* 760/1624 */  width33.3%;     /* 250/750 */  height2.46%;    /* 40/1624 */  font-size20px;  text-align: center;  color#4f708c;  background: transparent;  border: none;  outline: none;  caret-color#4f708c;}/* 性别对勾图片:默认隐藏,选中后显示 *//* x:200,y:900 / x:340,y:900 / x:500,y:900 */.checked-img {  position: absolute;  width6%;  top55.4%;       /* 900/1624 */  opacity0;  pointer-events: none;  transition: opacity 0.15s;}.checked-img.visible { opacity1; }#checked-male  { left26.67%; } /* 200/750 */#checked-femaleleft45.33%; } /* 340/750 */#checked-all   { left66.67%; } /* 500/750 *//* 성별 클릭 투명 열역 */.gender-tap {  position: absolute;  top53%;  width12%;  height8%;  cursor: pointer;  background: transparent;  border: none;}#tap-male   { left24%; }#tap-female { left43%; }#tap-all    { left64%; }/* 提交透明热区 shadownBt:[160,60,300,1006] */#submit-btn {  position: absolute;  left40%;        /* 300/750 */  top61.9%;       /* 1006/1624 */  width21.3%;     /* 160/750 */  height3.7%;     /* 60/1624 */  background: transparent;  border: none;  cursor: pointer;}/* ---- Poster 页 ---- */#poster-img {  position: absolute;  inset0;  width100%;  height100%;  object-fit: cover;  opacity0;  transition: opacity 0.5s;}#poster-img.visible { opacity1; }#poster-nickname {  position: absolute;  color#fff;  font-size13px;  text-align: center;  text-shadow0 0 8px rgba(0,0,0,0.3);  line-height1.2;  word-break: break-all;  pointer-events: none;  opacity0;  transition: opacity 0.3s;}#poster-nickname.visible { opacity1; }#tag-img {  position: absolute;  top0left0;  width100%;  opacity0;  transition: opacity 0.5s;}#tag-img.visible { opacity1; }#ctag-text {  position: absolute;  width100%;  text-align: center;  font-size17px;  color#434c5f;  letter-spacing0.1em;  opacity0;  transition: opacity 0.5s;}#ctag-text.visible { opacity1; }#save-tip {  position: absolute;  bottom20px;  left50%;  transformtranslateX(-50%);  colorrgba(255,255,255,0.8);  font-size12px;  white-space: nowrap;  opacity0;  transition: opacity 0.3s;}#save-tip.visible { opacity1; }/* 页面切换遮罩 */#transition-mask {  position: absolute;  inset0;  background#000;  opacity0;  pointer-events: none;  transition: opacity 0.4s;  z-index100;}/* 音乐按钮:雪碧图上帧=播放,下帧=暂停,无旋转 */#music-btn {  position: absolute;  top20px;  left20px;  width30px;  height30px;  border: none;  padding0;  cursor: pointer;  z-index9999;  backgroundurl('/proxy/musicBt.png') no-repeat center 0px;  background-size100% auto;}#music-btn.paused {  background-position: center -50px;}
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » H5源码复现 | 快手十三周年 – 你身体里住着怎样的少年

猜你喜欢

  • 暂无文章