简介:这是一份仿京东数码商城的纯前端动态网页源码,共20个文件,包含2个HTML页面、1个CSS样式表以及17张JPG/PNG图片素材,压缩包仅503KB。页面用HTML搭建商品陈列、导航栏等结构,CSS负责响应式布局和悬停、滑动等交互动效,JavaScript实现轮播、点击切换等动态功能,整体模拟了电商网站常见视觉与操作体验。适合正在学习HTML/CSS/JavaScript的初学者,或需要参考电商页面写成效的开发者,用来练习DOM操作、CSS伪类动画以及基础事件处理。素材图片已分类存放,便于替换和复用。该资源目前已获134人学习,对于想快速上手前端综合小项目、理解基础网页交互逻辑的读者,是一份轻量且完整的练手资料。
1. 仿京东数码网页动态效果:为什么拿京东当练手题
想做前端但手头没有像样的练手项目,很多人会把京东首页当成“靶子”。仿京东数码网页动态效果这件事,看起来是模仿一个电商首页,实际上是把轮播、秒杀倒计时、吸顶导航、悬浮卡片、返回顶部这些电商通用交互,用纯 css+html+JavaScript 完整过一遍。它不依赖任何框架,打开浏览器双击就能跑,改一改就能放进作品集或课程设计里。适合刚学完三件套、想练 CSS 动画和 DOM 操作的人,也适合需要快速搭一个“看起来挺专业”的演示页的人。这篇内容就按“布局 → CSS 动效 → JS 交互 → 踩坑 → 优化”的顺序,把每条命令和参数讲清楚。
2. 先搭京东式数码页布局:栅格骨架与 CSS 变量
京东数码页看起来信息密度高,但拆掉图片和文字,骨架并不复杂:顶部信息条、搜索区、导航、首屏轮播、秒杀倒计时、商品卡片流、底部。做仿制页不需要还原真实数据,重点是让布局有呼吸感、让后续动效有明确的挂载点。
2.1 说人话的 HTML 骨架:头部、搜索、导航、轮播、商品区
我一般会把页面按语义化标签切块,类名统一用jd-前缀,这样写 CSS 和 JS 时一眼能认出归属。下面是一个可直接复制的最小骨架,商品图用 CSS 渐变色块代替,避免依赖外链图片:
<!doctype html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>仿京东数码页</title> <link rel="stylesheet" href="css/style.css"> </head> <body> <header class="jd-header"> <div class="jd-header-inner"> <span class="jd-logo">JD 数码</span> <form class="jd-search" id="jdSearch"> <input type="text" placeholder="搜索笔记本 / 手机 / 耳机"> <button type="submit">搜索</button> </form> </div> </header> <nav class="jd-nav" id="jdNav"> <a href="#">手机</a> <a href="#">笔记本</a> <a href="#">平板</a> <a href="#">耳机</a> <a href="#">智能手表</a> </nav> <main class="jd-main"> <section class="jd-banner" id="jdBanner"> <div class="jd-banner-track"> <div class="jd-banner-slide active">Banner 1</div> <div class="jd-banner-slide">Banner 2</div> <div class="jd-banner-slide">Banner 3</div> </div> <button class="jd-banner-prev">‹</button> <button class="jd-banner-next">›</button> </section> <section class="jd-seckill" id="jdSeckill"> <h2>限时秒杀</h2> <div class="jd-seckill-timer"> <span id="seckillHours">00</span>: <span id="seckillMinutes">00</span>: <span id="seckillSeconds">00</span> </div> </section> <section class="jd-goods" id="jdGoods"> <!-- 商品卡片由 JS 渲染,这里只放容器 --> </section> </main> <footer class="jd-footer"> <p>仅供前端练手,不涉及任何真实商品与数据</p> </footer> <script src="js/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 class="jd-count" id="jdCount">.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 class="jd-seckill" id="jdSeckill">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一键返回顶部算法”的落点。现代浏览器直接使用平滑滚动 API:
backTopBtn.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 class="jd-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节流补上即可。做完这些,这个仿京东数码页的动态效果就算不是“完美还原”,也已经是能拿出手的作品了。
本文还有配套的精品资源,点击获取