在这里插入图片描述

小白前端速通:用CSS3 target伪类实现无JS交互效果(附实战技巧)

“写啥JavaScript?地址栏里加个#号就能让页面跳探戈,这才是浪漫的前端。”——某位不想加班的切图仔

为什么还在写JavaScript做简单交互?

先别急着翻React文档,也别急着npm install某个弹窗组件。
很多时候,我们只是想点一下按钮,让某个区域高亮、让某个盒子出现、让某个提示滚到视野中央——这些“小动静”真用不着把webpack请出来。

CSS3的:target伪类就是官方白送的“零依赖交互库”。它躲在规范角落里,安静得像图书馆后排的学霸,但只要你喊它的名字(在URL里丢一个#),它立刻站起来给你表演一段街舞。

今天这篇长文,就把这位学霸拉到舞台中央,跳满五支舞:导航高亮、选项卡、模态框、手风琴、呼吸灯。
全程无JavaScript,代码管饱,注释管够,复制粘贴就能跑。


认识target伪类这位低调高手

:target的户口本很简单:

  • 选择器语法::target
  • 监听对象:当前URL片段(hash)对应的元素
  • 激活时机:hash与元素id匹配的瞬间
  • 失效时机:hash被清空或换成别的id

一句话:谁的名字在地址栏#后面,谁就是:target的亲儿子,立刻继承你写的样式。

举个最直白的例子:

<!-- ① 导航 -->
<nav>
  <a href="#cat"></a>
  <a href="#dog"></a>
</nav>

<!-- ② 内容 -->
<article id="cat">我是一只猫</article>
<article id="dog">我是一只狗</article>
/* ③ 让被选中的动物变红 */
article:target {
  color: #e91e63;
  background: #fce4ec;
  padding: 1rem;
  border-radius: 8px;
}

点“喵”,地址栏变成...#cat<article id="cat">瞬间变粉;点“汪”,换狗变粉。
没有任何JS,却完成了“点击切换高亮”的经典交互。


target怎么工作?原理讲透不绕弯

浏览器内部有个“当前目标”插槽。
当你点击<a href="#id">,浏览器做三件事:

  1. #id塞进URL
  2. 滚动页面让id="id"的元素进入视口(默认行为)
  3. 把“当前目标”指向该元素,同时触发:target匹配

如果下一刻你用JS或者手动把hash改成#id2,老目标立刻失宠,新目标上位。
CSS是声明式语言,:target只是浏览器在样式计算阶段告诉它“现在谁是指定目标”,其余全靠开发者想象力。


动手试试:5个经典场景玩转target

下面每个场景都给“完整HTML+CSS”,直接扔文件里就能打开看效果。
为了阅读体验,代码折叠在注释里,复制时别漏行。


点击菜单项自动高亮当前页

需求:顶部导航三项,点谁谁高亮,且页面不跳转(纯单页感)。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>导航高亮</title>
  <style>
    /*  reset  */
    *{margin:0;padding:0;box-sizing:border-box;}
    body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;}

    /* 导航条 */
    nav{
      display:flex;
      background:#673ab7;
      padding:0 2rem;
    }
    nav a{
      color:#fff;
      text-decoration:none;
      padding:1rem 1.5rem;
      position:relative;
      transition:background .3s;
    }
    /* 默认hover */
    nav a:hover{background:#5e35b1;}
    /* 关键:谁被target,谁就长条高亮 */
    nav a:target{
      background:#9575cd;
    }
    /* 为了让链接本身成为target,需要给a标签加id */
    #nav-home:target{background:#9575cd;}
    #nav-work:target{background:#9575cd;}
    #nav-me:target{background:#9575cd;}

    /* 内容区随便写点 */
    section{padding:2rem;}
  </style>
</head>
<body>

<nav>
  <!-- 注意:href与id同名,点自己把自己变成target -->
  <a id="nav-home" href="#nav-home">首页</a>
  <a id="nav-work" href="#nav-work">作品</a>
  <a id="nav-me"  href="#nav-me">关于</a>
</nav>

<section>
  <p>点上面导航,紫色条会跟随。全程无JS。</p>
</section>

</body>
</html>

小技巧
如果嫌弃地址栏老出现#nav-home,可以用<a href="#/">回到顶部并清掉hash,高亮消失,实现“取消选中”。


无刷新切换选项卡内容

需求:经典“横向选项卡”,点标题换内容,不闪屏。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>纯CSS选项卡</title>
<style>
  .tabs{width:600px;margin:2rem auto;}
  /* 按钮排排坐 */
  .tabs>label{
    display:inline-block;
    padding:.6rem 1.2rem;
    background:#e0e0e0;
    cursor:pointer;
    margin-right:4px;
    border-radius:6px 6px 0 0;
    transition:background .25s;
  }
  /* 内容面板默认隐身 */
  .tabs>.panel{
    display:none;
    padding:1rem;
    background:#fafafa;
    border:1px solid #e0e0e0;
    border-radius:0 6px 6px 6px;
  }
  /* 关键:谁被target,谁显示 */
  #tab1:target ~ #panel1,
  #tab2:target ~ #panel2,
  #tab3:target ~ #panel3{
    display:block;
  }
  /* 同时把对应label高亮 */
  #tab1:target ~ [for="tab1"],
  #tab2:target ~ [for="tab2"],
  #tab3:target ~ [for="tab3"]{
    background:#fff;
    border-bottom:1px solid #fafafa;
  }
</style>
</head>
<body>

<div class="tabs">
  <!-- 隐藏的三兄弟,专门负责被target -->
  <a id="tab1"></a>
  <a id="tab2"></a>
  <a id="tab3"></a>

  <label for="tab1"><a href="#tab1">东京</a></label>
  <label for="tab2"><a href="#tab2">巴黎</a></label>
  <label for="tab3"><a href="#tab3">纽约</a></label>

  <div id="panel1" class="panel">东京的寿司真好吃</div>
  <div id="panel2" class="panel">巴黎可颂香到飞起</div>
  <div id="panel3" class="panel">纽约芝士蛋糕yyds</div>
</div>

</body>
</html>

解释

  • 用空<a>做“靶子”,放在最前,负责被hash激活
  • 后面的<label>里再包一个真正的链接,用户点的是它,hash变化触发前面的空<a>成为:target
  • 利用兄弟选择器~把对应的.panel显示出来

这样做的好处是:label文字样式随便玩,还能保持键盘无障碍。


纯CSS实现模态框弹出与关闭

需求:点击“登录”按钮,弹出遮罩+对话框,再点遮罩或关闭按钮消失。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>纯CSS模态框</title>
<style>
  body{font-family:-apple-system;}
  /* 触发按钮 */
  .trigger{
    display:inline-block;
    padding:.8rem 1.4rem;
    background:#2196f3;
    color:#fff;
    border-radius:4px;
    text-decoration:none;
  }
  /* 模态框本体:默认透明且点不到 */
  .modal{
    position:fixed;
    top:0;left:0;right:0;bottom:0;
    background:rgba(0,0,0,.4);
    display:flex;
    align-items:center;
    justify-content:center;
    visibility:hidden;
    opacity:0;
    transition:opacity .3s, visibility .3s;
  }
  /* 关键:当modal被target,现身 */
  .modal:target{
    visibility:visible;
    opacity:1;
  }
  /* 白底内容盒 */
  .modal-content{
    background:#fff;
    padding:2rem;
    border-radius:8px;
    width:90%;
    max-width:400px;
    position:relative;
  }
  /* 关闭按钮:右上角小红叉 */
  .close{
    position:absolute;
    right:1rem;top:1rem;
    font-size:1.5rem;color:#999;text-decoration:none;
  }
  /* 点遮罩背景也能关:整个modal层再包一层a */
  .modal>a.overlay{
    position:absolute;
    top:0;left:0;right:0;bottom:0;
    cursor:default;
  }
</style>
</head>
<body>

<!-- ① 触发 -->
<a class="trigger" href="#login-modal">登录</a>

<!-- ② 模态框 -->
<div id="login-modal" class="modal">
  <!-- 背景遮罩链接:点它等于访问#,清空hash -->
  <a href="#" class="overlay"></a>
  <div class="modal-content">
    <a class="close" href="#">×</a>
    <h3>用户登录</h3>
    <p>这里可以放真实表单</p>
  </div>
</div>

</body>
</html>

使用技巧

  • 把遮罩层本身做成链接href="#",点背景即清空hash,modal自动消失
  • 如果想“只能点按钮关闭”,把遮罩层的<a>去掉即可
  • 支持ESC关闭?很遗憾,纯CSS做不到,得靠JS监听键盘;但移动端没有ESC,反而更稳

手风琴式FAQ折叠展开

需求:一排问题列表,点一个问题,答案滑出,其它问题自动收起。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>纯CSS手风琴</title>
<style>
  .faq{width:640px;margin:2rem auto;}
  /* 问题条 */
  .faq summary{
    display:block;
    padding:1rem;
    background:#f5f5f5;
    border-bottom:1px solid #e0e0e0;
    cursor:pointer;
    font-weight:600;
    position:relative;
  }
  /* 小三角 */
  .faq summary::after{
    content:'▶';
    position:absolute;
    right:1rem;
    transition:transform .3s;
  }
  /* 答案默认收起 */
  .faq .answer{
    max-height:0;
    overflow:hidden;
    transition:max-height .4s ease;
    padding:0 1rem;
  }
  /* 关键:当条目被target,答案展开 */
  #q1:target ~ #a1,
  #q2:target ~ #a2,
  #q3:target ~ #a3{
    max-height:200px;
    padding:1rem;
  }
  /* 同时三角旋转 */
  #q1:target ~ summary[for="q1"]::after,
  #q2:target ~ summary[for="q2"]::after,
  #q3:target ~ summary[for="q3"]::after{
    transform:rotate(90deg);
  }
</style>
</head>
<body>

<div class="faq">
  <!-- 隐形靶子 -->
  <a id="q1"></a><a id="q2"></a><a id="q3"></a>

  <label for="q1"><summary><a href="#q1">CSS能取代JavaScript吗?</a></summary></label>
  <div id="a1" class="answer">不能,但能省掉80%的“小交互”。</div>

  <label for="q2"><summary><a href="#q2">target伪类兼容如何?</a></summary></label>
  <div id="a2" class="answer">IE9+全绿,放心浪。</div>

  <label for="q3"><summary><a href="#q3">如何关闭已展开项?</a></summary></label>
  <div id="a3" class="answer">再点一次或给个“全部收起”链接到#。</div>
</div>

</body>
</html>

思路
max-height过渡制造“滑梯”效果,比display:block/hidden更顺滑。
反向控制“仅展开一个”需要额外技巧,后面会讲。


页面内锚点跳转时加个呼吸灯效果

需求:点目录跳到对应段落,段落闪两下淡黄背景,提示用户“看这里”。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>锚点呼吸灯</title>
<style>
  /* 目录 */
  .toc{position:fixed;right:2rem;top:2rem;background:#fff;box-shadow:0 2px 8px rgba(0,0,0,.1);padding:1rem;border-radius:6px;}
  .toc a{display:block;margin:.5rem 0;color:#1976d2;}
  /* 段落 */
  article{max-width:600px;margin:4rem auto;line-height:1.8;}
  h2{margin-top:3rem;}
  /* 关键:被target时播放高亮动画 */
  h2:target{
    animation:highlight 1.2s ease 2;   /* 播放两次 */
  }
  @keyframes highlight{
    0%,100%{background:transparent;}
    50%{background:#fff9c4;}
  }
</style>
</head>
<body>

<nav class="toc">
  <a href="#sec1">第一章</a>
  <a href="#sec2">第二章</a>
  <a href="#sec3">第三章</a>
</nav>

<article>
  <h1>《如何不加班》</h1>

  <h2 id="sec1">第一章:需求评审要勇敢说“不”</h2>
  <p>……(此处省略两千字)</p>

  <h2 id="sec2">第二章:能用CSS就别上JS</h2>
  <p>……(继续省略)</p>

  <h2 id="sec3">第三章:早下班是为了早上线</h2>
  <p>……(再省略)</p>
</article>

</body>
</html>

动画细节
animation2表示播放两次,用户目光会被黄色背景闪两次吸引,然后自动消失,毫无负担。


target的闪光点和小脾气

优点

  1. 零运行时成本,性能天花板
  2. 天然支持键盘与屏幕阅读器(锚点跳转本就无障碍)
  3. 样式与结构解耦,UI一改全改
  4. 不写事件监听,减少内存泄漏风险

缺点

  1. hash变化会留历史记录,用户点返回键可能“误关”弹窗
  2. 无法传参、无法做条件判断(比如“登录后才可打开”)
  3. 依赖id,命名冲突会打架
  4. 默认锚点滚动可能打乱单页应用的路由逻辑

一句话:简单交互让它上,复杂业务让JS来。


真实项目里怎么用才不翻车?

  1. 单页应用先评估路由
    如果项目用Vue Router/React Router,hash模式本身占用了#,再拿:target做弹窗容易互相覆盖。
    解决方案:

    • 改用history模式,让出hash
    • 或者弹窗用input:checked方案,不抢hash
  2. 做降级 & 渐进增强
    先写:target版本,再用JS监听click给不支持的老浏览器加class兜底。

    .modal.show{visibility:visible;opacity:1;}
    
    // 粗略示例
    if(!CSS.supports('selector(:target)')){ /* 加class写逻辑 */ }
    
  3. 语义化加分
    导航高亮时,给当前链接加aria-current="page";弹窗打开时把焦点移到对话框内,关闭后焦点返回触发源。
    这些都不影响:target工作,却能让读屏软件用户爱你三千遍。

  4. 命名空间隔离
    大型项目里给id加前缀:id="ui-faq-q1"id="login-modal-trigger",避免与后端渲染内容冲突。


踩坑实录:那些年被target整懵的瞬间

场景症状真相解药
点击后页面滚到顶部模态框一闪然后整个页面跳浏览器默认行为:hash为#时滚动到0,0给触发链接加event.preventDefault()? 不,我们说不写JS。直接让href="#modal-id"而不是href="#"
多个target同时生效打开弹窗后背景颜色也变了两个元素id重名命名加前缀,构建阶段用eslint-plugin-jsx-a11y/anchor-ambiguous-text扫描
手机端点系统返回键关不掉弹窗用户狂点返回,history堆栈爆炸每打开一次弹窗就push一个hash,返回只是清hash,不会关闭打开弹窗时记录scrollTop,关闭时手动history.back();或者干脆用input:checked方案
刷新页面后弹窗还在用户分享链接,别人一打开就弹登录hash被保存到URL在JS端加判断:若hash匹配弹窗id且用户未登录,则自动清hash;纯CSS无法解决

让target更聪明的小技巧

  1. 利用:not(:target)反向控制兄弟元素
    想让“仅展开一个”手风琴?给所有面板默认max-height:0,再用:not(:target)重置它们:

    .panel{max-height:0;}
    .faq:has(:target) .panel:not(:target){max-height:0!important;}
    

    当然:has()兼容性还在路上,生产环境请先用JS加class。

  2. 配合transition做丝滑动画
    模态框别直接display:none/block,用visibility+opacity过渡;手风琴用max-heightgrid-template-rows过渡,性能更丝滑。

  3. 与自定义属性联动做主题切换

    html:target{
      --theme-color:#ff4081;
    }
    

    然后整站用var(--theme-color),实现“点按钮换主题”的魔法(当然仅限当前页)。

  4. label + input[type="radio"]做多状态
    当hash模式被路由占用时,可以用radio的:checked伪类接力,同样不写JS。思路与:target完全一致,只是把“hash”换成“radio选中值”。


写在最后(也没有结束)

:target不是银弹,却是前端工具箱里最轻便的那把瑞士军刀。
它教会我们:在合适的场景,用合适的技术,做刚刚好的事。

下次产品说“只要一个小小的弹窗”,你就可以自信地回:“不用打包依赖,不用写事件监听,给我十分钟,一行href搞定。”

然后合上电脑,去楼下买杯柠檬茶——早下班,从学会:target开始。

(代码都给你了,现在轮到你去让浏览器跳探戈。)

欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。

推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!


专栏系列(点击解锁)学习路线(点击解锁)知识定位
《微信小程序相关博客》 持续更新中~结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等
《AIGC相关博客》 持续更新中~AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结
《HTML网站开发相关》 《前端基础入门三大核心之html相关博客》前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识
《前端基础入门三大核心之JS相关博客》前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。
通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心
《前端基础入门三大核心之CSS相关博客》介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页
《canvas绘图相关博客》Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化
《Vue实战相关博客》持续更新中~详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅
《python相关博客》持续更新中~Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具
《sql数据库相关博客》持续更新中~SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能
《算法系列相关博客》持续更新中~算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维
《IT信息技术相关博客》持续更新中~作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识
《信息化人员基础技能知识相关博客》无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方
《信息化技能面试宝典相关博客》涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面
《前端开发习惯与小技巧相关博客》持续更新中~罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等
《photoshop相关博客》 持续更新中~基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结
日常开发&办公&生产【实用工具】分享相关博客》持续更新中~分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具

吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤

非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!

在这里插入图片描述

更多推荐