
简介这是一份仿京东数码商城的纯前端动态网页源码共20个文件包含2个HTML页面、1个CSS样式表以及17张JPG/PNG图片素材压缩包仅503KB。页面用HTML搭建商品陈列、导航栏等结构CSS负责响应式布局和悬停、滑动等交互动效JavaScript实现轮播、点击切换等动态功能整体模拟了电商网站常见视觉与操作体验。适合正在学习HTML/CSS/JavaScript的初学者或需要参考电商页面写成效的开发者用来练习DOM操作、CSS伪类动画以及基础事件处理。素材图片已分类存放便于替换和复用。该资源目前已获134人学习对于想快速上手前端综合小项目、理解基础网页交互逻辑的读者是一份轻量且完整的练手资料。1. 仿京东数码网页动态效果为什么拿京东当练手题想做前端但手头没有像样的练手项目很多人会把京东首页当成“靶子”。仿京东数码网页动态效果这件事看起来是模仿一个电商首页实际上是把轮播、秒杀倒计时、吸顶导航、悬浮卡片、返回顶部这些电商通用交互用纯 csshtmlJavaScript 完整过一遍。它不依赖任何框架打开浏览器双击就能跑改一改就能放进作品集或课程设计里。适合刚学完三件套、想练 CSS 动画和 DOM 操作的人也适合需要快速搭一个“看起来挺专业”的演示页的人。这篇内容就按“布局 → CSS 动效 → JS 交互 → 踩坑 → 优化”的顺序把每条命令和参数讲清楚。2. 先搭京东式数码页布局栅格骨架与 CSS 变量京东数码页看起来信息密度高但拆掉图片和文字骨架并不复杂顶部信息条、搜索区、导航、首屏轮播、秒杀倒计时、商品卡片流、底部。做仿制页不需要还原真实数据重点是让布局有呼吸感、让后续动效有明确的挂载点。2.1 说人话的 HTML 骨架头部、搜索、导航、轮播、商品区我一般会把页面按语义化标签切块类名统一用jd-前缀这样写 CSS 和 JS 时一眼能认出归属。下面是一个可直接复制的最小骨架商品图用 CSS 渐变色块代替避免依赖外链图片!doctype html html langzh-CN head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title仿京东数码页/title link relstylesheet hrefcss/style.css /head body header classjd-header div classjd-header-inner span classjd-logoJD 数码/span form classjd-search idjdSearch input typetext placeholder搜索笔记本 / 手机 / 耳机 button typesubmit搜索/button /form /div /header nav classjd-nav idjdNav a href#手机/a a href#笔记本/a a href#平板/a a href#耳机/a a href#智能手表/a /nav main classjd-main section classjd-banner idjdBanner div classjd-banner-track div classjd-banner-slide activeBanner 1/div div classjd-banner-slideBanner 2/div div classjd-banner-slideBanner 3/div /div button classjd-banner-prev‹/button button classjd-banner-next›/button /section section classjd-seckill idjdSeckill h2限时秒杀/h2 div classjd-seckill-timer span idseckillHours00/span: span idseckillMinutes00/span: span idseckillSeconds00/span /div /section section classjd-goods idjdGoods !-- 商品卡片由 JS 渲染这里只放容器 -- /section /main footer classjd-footer p仅供前端练手不涉及任何真实商品与数据/p /footer script srcjs/main.js/script /body /html这段结构有四个关键点。第一jd-banner-track是轮播的“轨道”后面 CSS 的位移和 JS 的切换都是作用在它上面第二jd-seckill-timer里面三个span分别放时分秒倒计时只更新这三个节点的textContent不重建 DOM第三商品区只放空容器卡片全部由 JS 渲染方便练习事件委托和动态模板第四搜索框用form而不是普通div按回车能触发 submit 事件这个细节在移动端键盘上体验差别很大。类名约定上我习惯用“区块-元素-状态”三层命名比如jd-banner-slide active里的active表示当前展示项。这样写 CSS 时可以直接用.jd-banner-slide.active精准控制状态不用额外加 data 属性。2.2 flex 换行布局与极简栅格为什么卡片流不用 grid商品卡片区的布局最常见的做法是flex-wrap配合固定百分比宽度而不是一上来就用 CSS Grid。原因很简单京东这种商品流卡片高度不固定标题可能一行也可能两行flex 换行会让卡片按内容自然撑开而 Grid 的行高由网格轨道决定卡片高度不齐时反而要写多余的align-items逻辑。.jd-goods-list { display: flex; flex-wrap: wrap; justify-content: space-between; row-gap: 24px; } .jd-goods-card { width: 23.5%; background: #fff; border-radius: 12px; padding: 16px; box-sizing: border-box; transition: transform 0.3s ease, box-shadow 0.3s ease; }justify-content: space-between会让最后一行不足四张卡片时“左右散开”这是电商页常见的处理方式。如果你希望最后一行左对齐可以在容器上加一个空的占位卡片或者改用justify-content: flex-start配合margin计算间隙。宽度写成23.5%是因为四张卡片要留出三处间隙(100% - 3 * 2%) / 4 ≈ 23.5%其中2%是间隙宽度。这个算法比width: 25%再扣 padding 更直观改间隙时只需要动一处。极简栅格系统同样可以用 flex 实现。比如需要一个“左侧商品图、右侧参数”的列表卡片时我给外层容器写display: flex; gap: 12px;左边固定120px右边flex: 1。这种做法比 grid 的grid-template-columns: 120px 1fr在语义上更贴合“导航 内容”的排列而且嵌套在商品卡片里不容易带出网格线的默认行为。2.3 CSS 变量管住“京东红”主题色与价格字体渐变京东主色调是红色系但直接在每个选择器里写#e1251b后面想统一调整会非常痛苦。我会在:root上定义一组 CSS 变量把颜色、圆角、阴影都集中管理动态效果里需要改的主题色也走变量。:root { --jd-red: #e1251b; --jd-dark: #222; --jd-gray: #999; --jd-bg: #f4f4f4; --jd-radius: 12px; --jd-shadow: 0 6px 20px rgba(0, 0, 0, 0.06); } .jd-header { background: var(--jd-red); } .jd-nav a:hover { color: var(--jd-red); border-bottom: 2px solid var(--jd-red); }价格数字是京东页面里最显眼的视觉元素很多人会直接用红色加粗但想要更细腻的质感可以用字体渐变。要让渐变作用于文字本身必须写background-clip: text并把文字颜色设为transparent否则渐变会被文字底色盖住。.jd-price { font-size: 22px; font-weight: 700; background: linear-gradient(135deg, #f10215 0%, #e1251b 45%, #ff6f61 100%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; color: transparent; }参数说明135deg是渐变方向从左上到右下三段颜色让渐变有“先深后浅再过渡”的层次。-webkit-background-clip是为了兼容仍使用 WebKit 内核的浏览器正式项目中我会把它和无前缀写法一起保留。需要注意background-clip: text对元素背景以外的部分没有影响所以这个价格文字不能同时设置背景色否则渐变范围会被背景色挡住。3. 纯 CSS 让首屏动起来轮播、涟漪光圈与流光边框骨架搭好之后进入标题里最吸引人的部分——动态效果。这一章先解决“不动也好看动了更抓眼”的 CSS 动效。能交给 CSS 的动效尽量不碰 JS因为 CSS 动画由浏览器合成器处理比 JS 改样式更省资源。3.1 首屏轮播先用 CSS 跑起来fade 动画与延迟参数轮播不只是 JS 的活。如果只做自动播放、不做手动点击纯 CSS 完全可以实现而且代码量极少。思路是让每一张 slide 叠加在同一个位置由animation控制不透明度依次亮起再熄灭。.jd-banner { position: relative; height: 360px; overflow: hidden; } .jd-banner-slide { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: linear-gradient(145deg, #1a237e, #4a148c); color: #fff; font-size: 28px; opacity: 0; animation: bannerFade 12s infinite; } .jd-banner-slide:nth-child(1) { animation-delay: 0s; } .jd-banner-slide:nth-child(2) { animation-delay: 4s; } .jd-banner-slide:nth-child(3) { animation-delay: 8s; } keyframes bannerFade { 0% { opacity: 0; } 4% { opacity: 1; } 28% { opacity: 1; } 32% { opacity: 0; } 100% { opacity: 0; } }关键参数三张图轮播每张展示 3 秒、切换淡入淡出 1 秒一个完整周期是 12 秒。每一张图占用周期的前 4 秒——1 秒淡入、3 秒显示、随后淡出——所以animation-delay依次是0s、4s、8s。keyframes里的4%是 4 秒除以 12 秒的结果28%是第 3.36 秒的位置留了一点淡出的时间。如果你改成四张图周期要变为 16 秒每个节点的百分比也要重新算(100% / 张数) * 当前序号。这种方案有个边界纯 CSS 轮播没有“暂停”概念鼠标悬停想让画面停下来需要配合 JS 或者用animation-play-state: paused加:hover选择器。后者更干净但我见过不少人在 JS 里写clearInterval去停 CSS 动画那是多余的。3.2 css 鼠标移入事件卡片悬浮的 transform 与 shadow商品卡片悬浮效果是京东页面里最常见的交互反馈很多初学者用 JS 监听mouseenter/mouseleave去切换类名其实 CSS 的:hover就是天然的鼠标移入事件性能和代码量都更好。.jd-goods-card { transform: translateY(0); box-shadow: 0 0 0 rgba(0, 0, 0, 0); } .jd-goods-card:hover { transform: translateY(-6px); box-shadow: 0 12px 28px rgba(0, 0, 0, 0.12); }translateY(-6px)是经验值小于 4px 感觉不到大于 10px 会显得卡片“飘”起来。box-shadow的0 12px 28px分别表示水平偏移、垂直偏移、模糊半径颜色用rgba带透明度比纯黑更自然。这两个属性都只触发合成层不会引起重排动画完全交给transition: transform 0.3s ease, box-shadow 0.3s ease即可。这里有个反直觉的点transition写在默认状态而不是:hover里。写在默认状态意味着鼠标移入和移出都有过渡动画如果只写在:hover里移出时会瞬间弹回。这个小细节经常让页面出现“上去慢悠悠、下来瞬间跳”的违和感。3.3 流光边框与涟漪光圈扩散两个氛围动效的边界这两个动效常见于促销卡片和按钮上。流光边框让边框颜色像光束一样循环扫过涟漪光圈则让一个圆环从中心扩散并淡出。它们都有一个共同原则不修改布局属性只做视觉层动画。流光边框的经典做法是让边框背景渐变循环移动。我会用一个两层结构外层元素用padding: 2px让渐变背景透出来内层元素用纯色背景盖住中间区域只露出窄边框。.jd-flash-card { position: relative; padding: 2px; border-radius: 14px; background: linear-gradient(120deg, #e1251b, #ff9a76, #e1251b); background-size: 200% 200%; animation:流光Move 3s linear infinite; } .jd-flash-card-inner { background: #fff; border-radius: 12px; padding: 20px; } keyframes 流光Move { 0% { background-position: 0% 50%; } 100% { background-position: 200% 50%; } }注意代码里的“流光Move”是中文动画名CSS 支持中文标识符但我后来改项目时发现团队其他成员不习惯最终统一改成shimmerMove。background-size: 200% 200%是让背景比元素宽一倍background-position从0% 50%移到200% 50%时就形成一个完整的循环扫过。这种方案比conic-gradient兼容性好也比旋转伪元素的border-color动画省心。涟漪光圈扩散适合做“限时秒杀”或“新品首发”按钮的聚焦效果。核心是伪元素::after的scale配合opacity.jd-pulse-btn { position: relative; padding: 10px 24px; background: var(--jd-red); color: #fff; border: none; border-radius: 30px; cursor: pointer; } .jd-pulse-btn::after { content: ; position: absolute; inset: 0; border-radius: 30px; border: 2px solid var(--jd-red); animation: pulseRing 1.6s ease-out infinite; } keyframes pulseRing { 0% { transform: scale(1); opacity: 1; } 100% { transform: scale(1.35); opacity: 0; } }inset: 0让伪元素和按钮完全重叠border-radius保持一致圆环才能均匀扩散。scale(1.35)控制扩散范围按钮越大气泡越明显。这个动效被简写成“css涟漪光圈扩散”实际是两组动画的组合一次缩放加一次透明度的衰减。注意伪元素不能继承padding所以用inset:0直接把整个按钮区域占满不要加尺寸。3.4 数字加载动画效果 css纯 CSS 的局限与 JS 补位很多人搜“数字加载动画效果 css”期望用一个纯 CSS 方案让数字从 0 涨到目标值。真实情况是纯 CSS 很难逐位改变文本内容除非用property注册自定义属性配合counter计数器但这个特性的浏览器兼容性很一般生产环境不敢直接用。所以我不硬凹纯 CSS改用“JS 改数字 CSS 做过渡”的组合这也是纯三件套里最小可靠的方案。div classjd-count idjdCount>.jd-count { font-size: 36px; font-weight: 700; color: var(--jd-red); transition: transform 0.2s ease; }const countEl document.getElementById(jdCount); const target parseInt(countEl.dataset.target, 10); let current 0; const step Math.ceil(target / 60); const timer setInterval(() { current step; if (current target) { current target; clearInterval(timer); } countEl.textContent current.toLocaleString(); }, 30);逻辑说明用setInterval每 30 毫秒运行一次总时长大约 1.8 秒走完。Math.ceil(target / 60)是把目标值均分成 60 份然后每帧累加一份。toLocaleString()是 JavaScript 内置方法让数字显示成9,999的千分位格式不用自己写正则。参数说明30 毫秒是显示器的 2 帧间隔视觉上足够平滑60 份是经验值目标值越大每步增量越大动画看起来就越接近“加速跑”。CSS 在transition: transform上其实没参与数字变化它参与的是数字每次更新时给人眼的“跳动”过渡——数值快速变化时这个 transition 能让数字有轻微的位移反馈掩盖掉刷新感的生硬。4. JavaScript 激活交互秒杀倒计时、吸顶导航与返回顶部CSS 管住了“自动发生的动效”但用户主动触发的交互、以及需要实时计算的状态必须交给 JavaScript。这一章是标题里JavaScript分量最重的部分也是把页面从“展示”推向“可用”的关键。4.1 js css 倒计时用时间戳差值而不是每秒减一电商页的秒杀倒计时最容易翻车的写法是“每秒把秒数减 1”。这个写法在页面不刷新时看不出来问题但只要用户切换标签页浏览器为了省电会挂起定时器回来时倒计时就慢了。正确做法是只存一个结束时间戳每次刷新都拿当前时间跟它做差值。section classjd-seckill idjdSeckill>const seckillEl document.getElementById(jdSeckill); const endTime new Date(seckillEl.dataset.end).getTime(); function renderSeckill() { const diff endTime - Date.now(); if (diff 0) { seckillEl.querySelector(.jd-seckill-timer).textContent 已结束; return; } const hours Math.floor(diff / 3600000); const minutes Math.floor((diff % 3600000) / 60000); const seconds Math.floor((diff % 60000) / 1000); const pad (n) String(n).padStart(2, 0); document.getElementById(seckillHours).textContent pad(hours); document.getElementById(seckillMinutes).textContent pad(minutes); document.getElementById(seckillSeconds).textContent pad(seconds); } renderSeckill(); setInterval(renderSeckill, 1000);关键点在diff的计算endTime - Date.now()用的是时间戳单位是毫秒。Math.floor(diff / 3600000)把毫秒换算成小时diff % 3600000是扣掉整小时后的余数再除以60000得到分钟。padStart(2, 0)保证 1 秒显示成01避免倒计时宽度跳动。>const nav document.getElementById(jdNav); const backTopBtn document.getElementById(jdBackTop); let ticking false; function onScroll() { const y window.scrollY; nav.classList.toggle(is-sticky, y 260); backTopBtn.classList.toggle(is-visible, y 600); } window.addEventListener(scroll, () { if (!ticking) { window.requestAnimationFrame(() { onScroll(); ticking false; }); ticking true; } }, { passive: true });配套的 CSS 片段.jd-nav.is-sticky { position: fixed; top: 0; left: 0; right: 0; z-index: 100; background: #fff; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); } .jd-back-top { position: fixed; right: 30px; bottom: 60px; width: 44px; height: 44px; border-radius: 50%; background: rgba(0, 0, 0, 0.5); color: #fff; border: none; cursor: pointer; opacity: 0; pointer-events: none; transition: opacity 0.3s ease; } .jd-back-top.is-visible { opacity: 1; pointer-events: auto; }requestAnimationFrame在这里是节流器scroll事件可能一帧内触发多次但页面渲染每帧只执行一次sick标记保证一次渲染周期内只跑一次onScroll。passive: true告诉浏览器“这个监听器不会调用 preventDefault”浏览器看到这个标志后可以不做额外的取消判断滚动性能会好很多尤其是低端安卓机。返回顶部按钮本身就是“html一键返回顶部算法”的落点。现代浏览器直接使用平滑滚动 APIbackTopBtn.addEventListener(click, () { window.scrollTo({ top: 0, behavior: smooth }); });旧式方案是手动写定时器每帧把scrollY减去一个固定步长类似// 旧方案定时器步进已不推荐 const step () { if (window.scrollY 0) { window.scrollBy(0, -80); requestAnimationFrame(step); } };新方案的好处是不用手动计算步长behavior: smooth的缓动曲线由浏览器处理。对于老项目或需要自定义缓动的场景定时器步进仍可用但步长-80是固定值页面越长滚回顶部越慢最好改成基于当前滚动位置的比例步长。4.3 手动轮播与自动播放共存setInterval 的清理与重启纯 CSS 轮播适合“只看不点”的场景但电商轮播需要左右按钮手动切换切完还得自动播下去。这里的关键是定时器管理手动切换时先clearInterval完成切换后再重新启动否则自动播放会和你手动点到的位置错位。const track document.querySelector(.jd-banner-track); const slides document.querySelectorAll(.jd-banner-slide); const prevBtn document.querySelector(.jd-banner-prev); const nextBtn document.querySelector(.jd-banner-next); let currentIndex 0; let autoTimer; function goTo(index) { currentIndex (index slides.length) % slides.length; track.style.transform translateX(-${currentIndex * 100}%); } function startAuto() { autoTimer setInterval(() { goTo(currentIndex 1); }, 4000); } function stopAuto() { clearInterval(autoTimer); } prevBtn.addEventListener(click, () { stopAuto(); goTo(currentIndex - 1); startAuto(); }); nextBtn.addEventListener(click, () { stopAuto(); goTo(currentIndex 1); startAuto(); }); startAuto();goTo里用currentIndex slides.length再取模是为了处理“第一张点上一张”时索引不出现负数。translateX(-${currentIndex * 100}%)是让轨道水平移动一整屏宽度前提是每一张 slide 都占满容器宽度。4000 毫秒的自动播放间隔是京东页面常见的节奏太短来不及看太长显得页面死板。手动点击后立即重启自动播放会让连续操作的用户觉得页面很跟手。这个方案有一个注意点你在第 3 章写的纯 CSSbannerFade动画也必须移除因为opacity动画和transform位移动画会在同一组元素上冲突结果就是画面“又闪又滑”两个逻辑互相打架。做手动轮播时轨道位移方案和淡入淡出方案只能留一个。4.4 事件委托绑定动态商品卡片让 render 出来的节点可点击商品卡片如果是 JS 渲染出来的直接给每个卡片绑click会带来两个问题重新 render 后监听器丢失卡片数量多时内存开销也大。事件委托把监听器绑在父容器上利用事件冒泡机制捕获子元素一劳永逸。const goodsContainer document.getElementById(jdGoods); const goodsData [ { name: 降噪耳机, price: 899, tag: 热卖 }, { name: 机械键盘, price: 499, tag: 新品 }, { name: 4K 显示器, price: 1999, tag: 秒杀 }, { name: 无线鼠标, price: 129, tag: 满减 } ]; goodsContainer.innerHTML goodsData.map(item a classjd-goods-card href#>.fx-fade { animation: fadeIn 0.6s ease forwards; } .fx-slide-up { animation: slideUp 0.5s ease forwards; } .fx-pulse { animation: pulseRing 1.6s ease-out infinite; }这个文件只放动画和动效不掺布局。JS 里需要触发动画时只负责往元素上挂类名切走时移除类名不关心动画具体细节。这样哪个动效出问题直接去fx.css里找不用在整个项目里搜keyframes。这个习惯帮我在页面做到后期时省了非常多时间希望也能帮到你。6.2 验证动态效果是否流畅别只凭肉眼肉眼觉得“还行”跟“真正流畅”之间隔着一次性能实测。Chrome DevTools 的 Rendering 标签页里勾选 “Frame Rendering Stats”页面上会出现实时帧率再做一次滚动和连续点击看 FPS 是否稳定在 50 以上。低于 40 的时候优先检查有没有在动画里使用了top、left、width、height这些触发重排的属性把它们替换成transform。其次是检查有没有大面积的box-shadow动画阴影模糊在低端机上极耗性能。页面底部的返回顶部按钮双击时如果滚动出现抽搐通常是scroll监听器里做了复杂计算把requestAnimationFrame节流补上即可。做完这些这个仿京东数码页的动态效果就算不是“完美还原”也已经是能拿出手的作品了。本文还有配套的精品资源点击获取