简介:基于Bootstrap的响应式旅游行业网站模板,面向旅游网站开发者与前端学习者,可快速搭建适配桌面、平板和手机的旅游平台界面。模板整合导航、景点展示、套餐介绍、评论与预订表单等常见模块,兼顾视觉表现与交互体验,特别适合旅行社、景区或OTA类项目的起步版本。压缩包共37个文件,包含HTML页面、CSS样式、JavaScript脚本、JPG/PNG图片素材以及MD使用说明,整体仅1.77MB,结构紧凑、便于部署和二次修改。目前已有182人学习下载,适合初学者参照源码理解Bootstrap网格系统、响应式设计及组件调用方式,也适合开发者在此基础上定制品牌风格、扩展业务功能,快速产出可直接上线的旅游行业站点。
1. 做好了PC版却撑不起手机端浏览,旅游站模板的响应式改造从哪入手
很多旅游站的第一版是由传统外包或运营临时套 CMS 主题做出来的,办公室电脑上看着整整齐齐,手机上一打开,导航栏挤成一团、价格表格横向溢出、轮播图被拉伸变形、预订表单在键盘弹起后完全没法操作。标题里这套「Bootstrap响应式旅游行业网站模板-Travel」要解决的就是这类问题:用 Bootstrap 的栅格体系、响应式组件和工具类,把地接社、酒店、景区的页面在手机、平板和桌面三档宽度下重新组织一遍。它适合接手旧站改版的前端新人,也适合要从零搭静态活动页、不想自己造轮子的开发,更重要的是适合想搞清楚 Bootstrap 在真实业务里怎么用对、用透的从业者。下面按搭骨架、做组件、改视觉、验收四个环节讲一套可以直接落地的方案。
2. 栅格是地基:用Bootstrap栅格规划酒店列表与目的地卡片
2.1 先确定断点策略:旅游页面通常只要三档宽度
一个信息密度高的旅游列表页,最常见的问题是在 1024px 到 1280px 之间卡片一排四张显得拥挤,到了 768px 又突然堆叠成一条长流。Bootstrap 5 的默认断点分配是<576px(手机竖屏)、≥576px(大手机横屏)、≥768px(平板)、≥992px(桌面)、≥1200px(大桌面)、≥1400px(超大屏)。做旅游内容页我不会全层铺开,按三档规划即可:mobile(0-767px)看单列,tablet(768px-1199px)看两列,desktop(≥1200px)看四列。后面三档以外的细节配置可以留给后续迭代,模板交付到三档对绝大多数运营场景已经够用。
确定断点的依据不是设计稿,而是内容形态。旅游产品卡片里通常包含图片、标题、价格和两个按钮,标题长度还带目的地名称和产品名,窄屏单列展示可以把信息读全,中屏两列能在舒适阅读前提下增加曝光,桌面四列则充分利用宽屏并保证卡片最小宽度不低于 260px。这个最小宽度很重要,低于它价格和按钮就很难在同一行排开,我一般通过 col 的断点类来控制,而不是靠媒体查询二次改写。
2.2 卡片列表的最小组合:row + col + 响应式通用类
先写一个不带真实图片的目的地卡片网格,重点是把栅格类名和内容拆分看明白:
<div class="container py-4"> <div class="row g-3"> <div class="col-12 col-md-6 col-xl-3"> <div class="card h-100"> <img src="img/dest-01.jpg" class="card-img-top" alt="京都秋季庭院"> <div class="card-body"> <h5 class="card-title text-truncate">京都经典庭院三日</h5> <p class="card-text text-secondary small">古都文化主题,含住宿与当地交通安排</p> <div class="d-flex justify-content-between align-items-center"> <span class="text-danger fw-bold">¥4280</span> <a href="#" class="btn btn-sm btn-outline-primary">查看行程</a> </div> </div> </div> </div> <!-- 后续并列的 col-12 col-md-6 col-xl-3 结构相同 --> </div> </div>这段代码里,col-12 col-md-6 col-xl-3决定了卡片在不同宽度下的列数:手机端占满整行,平板从 md 开始每行两张,桌面从 xl 起每行四张。g-3是 Bootstrap 5 里的栅格沟槽控制,比老写法no-gutters加 margin 要清爽得多,水平和垂直间距统一由它接管。h-100保证同一行卡片高度一致,价格和按钮用 flex 布局分布在两端,是旅游套餐卡片最常见的表现方式。
这里有个新手容易忽略的点:栅格必须放在.row里的.col-*才生效,row自带负 margin 抵消容器 padding。如果直接在某张卡片上单独套col-md-6又没包 row,窄屏下样式会乱。千万不能把 Bootstrap 的栅格类当作随手加的独立样式,它们的语义是「列必须从属于行」。
2.3 图片响应式与卡片高度坑
旅游站对图片要求高,但模板里最容易翻车的就是img标签。首先是记得给每个列表图片加class="img-fluid",否则图片在窄屏按原始宽度溢出。其次是卡片列内部的图片高度不一致导致「高低脚」,我的习惯是把图片容器固定比例裁剪,用 Bootstrap 5 里的ratio ratio-16x9这个工具类:
<div class="ratio ratio-16x9"> <img src="img/dest-02.jpg" alt="北海道路线起点" class="object-fit-cover"> </div>注意object-fit-cover是 Bootstrap 5 提供的语义类,早期版本要手写 CSS。这样处理之后,无论后端回传图片是横版还是竖版,卡片高度始终稳定,排布就不会出现豁口。做轮播头图时同样适用,下一章会接着讲轮播怎么和这种比例容器配合。
图片处理还有一层容易踩的坑:ratio容器的子元素需要设置为position: absolute才能填满容器吗?Bootstrap 官方实现是.ratio > *被定为绝对定位,但img作为媒体元素不缺省设置,某些浏览器下它仍会按原始尺寸参与布局。稳妥做法是在图片上同时加w-100 h-100,确保绝对定位后的填充生效。视觉上还要注意object-position,默认居中裁剪,对带人物的场景照人脸容易裁掉,旅游运营图通常主体偏中下,我会给个别图片加object-position: 50% 80%做调整。
2.4 栅格折叠时排序怎么变
窄屏下列表顺序不能照搬桌面。运营经常要求手机端第一个推爆款,到了桌面爆款回到第三位。用order-first与order-lg-*可以做到不加一句 JS:
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-4 g-3"> <div class="col order-first order-lg-0">爆款套餐</div> <div class="col">普通套餐</div> </div>order-first表示默认排最前,order-lg-0在 lg 及以上还原为 DOM 顺序。row-cols-*是另一个容易漏掉的简写:它作用于外层容器,把子列均分,配合 col-* 混用要注意优先级,混合时col的宽度权重会覆盖容器均分,所以要么统一用容器侧均分,要么全部用 col-* 显式指定列宽,避免团队协作时出现规则冲突。
排序方法配合栅格重排可以解决「PC 首屏要放品牌区、手机首屏要放特价区」的冲突,但不要过度使用。两个及以上order-*混排会让 DOM 结构和视觉顺序完全脱节,后续维护的人看代码会非常痛苦。我通常只在首页顶部流量位和列表页前两个商品上做这个操作,其余保持 DOM 顺序。
3. 下拉菜单与轮播:套餐页和活动页常用的响应式交互组件
3.1 静态下拉菜单的数据属性驱动方式
主站导航里的「目的地」下拉,很多人以为必须写 JavaScript。Bootstrap 的 dropdown 完全可以用><li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" role="button"><nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm sticky-top"> <div class="container"> <a class="navbar-brand" href="#">Travel</a> <button class="navbar-toggler" type="button"><div id="heroCarousel" class="carousel slide carousel-fade"><form class="row g-3 needs-validation" novalidate> <div class="col-md-6"> <label for="name" class="form-label">姓名</label> <input type="text" class="form-control" id="name" required minlength="2"> <div class="invalid-feedback">请至少输入两个字。</div> </div> <div class="col-md-6"> <label for="travelDate" class="form-label">出发日期</label> <input type="date" class="form-control" id="travelDate" required> <div class="valid-feedback">日期可用</div> </div> <div class="col-12"> <button class="btn btn-primary" type="submit">提交预订信息</button> </div> </form> <script> document.querySelectorAll('.needs-validation').forEach(function (form) { form.addEventListener('submit', function (event) { if (!form.checkValidity()) { event.preventDefault(); event.stopPropagation(); } form.classList.add('was-validated'); }); }); </script>
novalidate禁用了表单的默认提交校验,避免浏览器弹原生气泡,之后交给 JS 统一处理。invalid-feedback在 input 触犯伪类时显示,valid-feedback在通过时显示。这套机制与 Bootstrap 内部验证设计保持一致:不需要在输入事件里逐字段判断,全部交给checkValidity()。需要特别说明的是,valid-feedback在was-validated之后,只有表单里被触碰过的:valid控件才会显示绿色,未触碰的不会自动显绿。
移动端表单有个额外问题:日期输入框在 iOS 上弹原生滚轮,Android 上弹日历控件,两者触发元素可能不同。如果有自定义清空按钮,注意不要让这个按钮成为form-control的一部分。键盘弹起遮挡下层输入框的问题,Bootstrap 自身不管,模板里要在input上避免使用position: fixed的容器包裹,否则 iOS 下会出现输入框错位。
4.2 人数与天数控件:select 和 number 的取舍
旅游表单几乎必填的几人成团、行程天数,用<select>或数字输入框各有取舍。用<select>好维护选项,但选项一多滚动麻烦;用<input type="number">在手机端弹的是数字键盘,输入舒适度高但校验逻辑要多写。折中做法是把天数和出发地拆开,减少长表单的顿挫感:
<div class="row g-3"> <div class="col-6"> <label for="dayCount" class="form-label">行程天数</label> <select id="dayCount" class="form-select" required> <option value="">请选择</option> <option value="3">3天</option> <option value="5">5天</option> <option value="7">7天及以上</option> </select> </div> <div class="col-6"> <label for="groupSize" class="form-label">成人人数</label> <input type="number" id="groupSize" class="form-control" min="1" max="20" value="2" required> </div> </div>form-select是和form-control并列的 Bootstrap 选择器样式,自带箭头,不需要再引入第三方选择器组件。给 select 加required,默认第一项设为value=""的空 option,这样「未选择」时checkValidity()会直接拦截提交。手机端数字键盘会把min/max当作输入合法性的补充提示,但不会绝对阻止输入,所以最终还是要靠checkValidity()回兜。
这里有一个建议类写法:人数和天数并排用col-6是合理的,但两个都放一行在窄屏下可能偏挤。可以改成col-12 col-sm-6,让 576px 以下各占一行,按钮区中间留出足够间距。触控目标太小是旅游站表单移动端跳出率高的重要原因,建议所有输入框高度保持在 44px 以上,Bootstrap 默认的form-control在移动端也够,但加上自定义的 select 后要检查箭头区域的点击热区。
4.3 价格表格必须做响应式包裹,否则表格把页面撑爆
行程报价多列、每列带币种符号和短横线,一旦超过视口宽度,手机端就是灾难。Bootstrap 给出的方案是把表格放在.table-responsive容器里,让表格本体横向滚动而不是缩放变形:
<div class="table-responsive"> <table class="table table-striped align-middle"> <thead class="table-light"> <tr><th>房型</th><th>早晚餐</th><th>3天</th><th>5天</th><th>7天</th><th>单房差</th></tr> </thead> <tbody> <tr><td>标准双床</td><td>含双早</td><td>¥1980</td><td>¥3180</td><td>¥4580</td><td>¥360/晚</td></tr> <tr><td>海景大床</td><td>含双早</td><td>¥2680</td><td>¥4180</td><td>¥5980</td><td>¥520/晚</td></tr> </tbody> </table> </div>这种横向滚动方案在移动端体验是:用户可以左右滑、列保持对齐,比把表格纵推成卡片更容易扫描比价。但要注意父级不要嵌套两层.table-responsive,嵌套会让外层表格内容被固定,手势指定又容易误触发整个页面的左右滑。如果同时存在多个表格块,建议给每个.table-responsive加一个tabindex="0"和明确的边界阴影,指示用户可以横向滑动。
还有一种更强的做法是:在窄屏下用display: block把每行tr变成卡片,td用::before伪造标题。Bootstrap 纯 CSS 不帮忙做这个,需要你写少量媒体查询。但注意,表格语义化维护成本高,且营销数据经常要导出,我一般只在「3天/5天/7天」这类固定列宽、内容短的报价表上保留横向滚动方案,复杂产品对比表再考虑反 turns 卡片变形。建议模板默认保留.table-responsive,同时告诉后端接口返回的数据列数尽量控制在 6 列以内。
5. 换肤不要硬写CSS:用Sass变量定制旅游主题色
5.1 为什么直接改官方 CSS 会到处漏色
拿到 Bootstrap 编译版后,新手最容易做的事是全局搜索btn-primary然后覆盖background-color,结果按钮是换色了,但下拉选中背景、轮播指示器、分页高亮和链接颜色还留着主题蓝。因为 Bootstrap 内部把大量组件的强调色都挂在$primary变量上,逐一手改 class 会全面陷入维护泥潭。旅游行业模板的主题色通常是海蓝、生态绿或沙棕色,选一个当品牌主色,然后所有状态色从它派生,这才是设计和工程上最稳的路径。
正确做法是在 Sass 源码层覆盖变量,再重新编译。源码里的_variables.scss定义了所有主题令牌,$primary被按钮、链接、选中态、轮播激活圆点、分页高亮、校验绿边等几十处引用。你改这一个变量,全站主色整体联动放开,这也是 Sass 方案比直接覆盖 CSS 强的地方。
5.2 在项目里接入 Sass 编译:最小可用方式
不引入 Vue/Webpack 全套工具链,用 Node 端跑 sass 就好。工程结构上我建议单独放scss/theme.scss,把变量声明放在 Bootstrap 源码 import 之前:
// scss/theme.scss $primary: #0f6b5f; $danger: #c94b4b; $enable-rounded: false; $border-radius: 0.25rem; $font-family-base: "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif; @import "../vendor/bootstrap-5.3/scss/bootstrap";$primary控制按钮、链接、选中态、轮播激活圆点等一大族颜色。$enable-rounded: false关闭圆角,按钮、卡片、输入框都更符合简洁的旅游品牌调性。把 Bootstrap 源码放进vendor目录而不是引 CDN 编译版,是因为你无法在运行时覆盖源码里的变量,CDN 版只能靠外挂 override 样式硬覆盖,遇到内部选择器权重问题会很被动。
编译命令用:
sass --watch scss/theme.scss css/theme.min.css --style=compressed--watch让 sass 在文件变化时自动重建,--style=compressed产出压缩产物。团队协作时再把这个命令包进 npm script 或 Makefile 里,底层仍是同一个 sass 流程,不需要引入框架环境。
5.3 变量没生效的四种排查情况
Bootstrap 变量系统的层级是结构化的:$primary是装饰层,$body-bg是基础层,组件层会再看自身的专属变量。四种情况是变量覆盖最常见的翻车现场:
- 只改
$primary没改$link-color:低层级组件的链接颜色还是默认蓝色。 - 只想改按钮圆角,只改
$btn-border-radius会失效,它受$enable-rounded的全局开关统一控制,圆角修改链路是$enable-rounded→$border-radius→ 组件变量。 - 字体族改了
$font-family-base,但标题h1-h6走的是$headings-font-family,它不继承$font-family-base而是有独立默认值。 - 卡片阴影类是
$box-shadow,它是一个 Sass 列表,如果你拼字符串进去会编译报错,要写成标准列表格式。
排查顺序建议是:查这个组件的官方 Sass 文档确认它用的变量名 → 看_variables.scss里的定义和!default逻辑 → 编译后搜索对应规则,确认最终生效的规则是否来自自己的文件。大多数换肤翻车原因,定位到后来都是这四类之一。
5.4 用 CSS 变量做轻量切换的必要限制
Bootstrap 5 引入了许多 CSS 变量,理论上可以在 runtime 切换主题色,比如--bs-primary。但组件内部很多样式在编译时就把值写进了具体类里,不会全部联动变换。如果你只需要「一键切换绿色/蓝色」的运营需求,可以接受部分组件颜色不变,那可以靠style标签覆盖:
body.theme-ocean { --bs-primary: #1766a5; --bs-link-color: #1766a5; }这个方案适合「只换主按钮、链接、部分背景」的轻换肤。但轮播指示器、分页激活、表单选中等组件,很多是用 Sass 编译后的具体颜色值,CSS 变量覆盖不到。复杂换肤必须回到 Sass 重新编译,这也是我建议模板交付时就把scss源文件一起提交的原因。运行时换肤更适合给品牌方做 AB 测试,不适合做主样式切换。
6. 上线前这样验收:直接在浏览器里模拟手机、弱网与图片负载
页面本地没问题,换真机交互失效的情况,十次里七八次都是缺移动端专项验收。我给模板设定的验收顺序按风险从高到低排:第一看导航折叠后的二级菜单能否正常打开,很多模板只折叠一级菜单,子菜单在触屏没有 hover 就永远出不来;第二看表单在手机上凑近点按的反馈,按钮过小、日期控件被键盘顶出视口是高频问题;第三看轮播和卡片图片的加载顺序,不设置的话首屏之外的图全部预载,弱网下首屏白屏时间会很长。
操作上,打开 Chrome DevTools 的 Device Toolbar,选 iPhone 12 Pro 这类机型模拟,然后切到 Network 面板里的 Fast 3G 做一次慢网验证:img[loading="lazy"]是否对首屏之外图片生效,轮播自动播放是否在弱网时出现白屏间隙。如果轮播第一屏就要快,把首图直接fetchpriority="high",其余图片加loading="lazy":
<img src="img/hero.jpg" class="d-block w-100 object-fit-cover" alt="首页主图" fetchpriority="high"> <img src="img/card-01.jpg" class="d-block w-100 object-fit-cover" alt="目的地卡片" loading="lazy" decoding="async">fetchpriority="high"让主图优先下载,loading="lazy"让卡片图滚动到视口附近时才请求,decoding="async"让解码不阻塞渲染。这三项是模板里性价比最高的基础性能优化,不需要打包工具介入。另一个容易漏掉的验收动作是把页面缩放到 320px 宽度扫一遍,长单词、表格td、轮播标题的white-space: nowrap都会现形。处理办法是给img和容器统一加强约束,遇到无法缩放的 flex 元素使用min-width: 0,而不是在外层套overflow-x: hidden去掩盖溢出,后者在 iOS Safari 上会造成整个页面横滑的漂移感。按这个顺序跑完,再交到运营手上,报 bug 的概率会低很多。
本文还有配套的精品资源,点击获取