使用Hotwire时如何高亮当前激活的导航链接?
Turbo环境下高亮当前菜单链接的解决方案
问题本质
你的代码失效是因为使用data: { turbo_frame: :main, turbo_action: 'replace' }后,Turbo仅替换页面的main区域,导航所在的头部没有被重新渲染,服务器端的current_page? helper无法重新执行,导致高亮状态无法更新。
可行解决方法
方法一:通过JavaScript监听Turbo事件动态更新高亮
利用Turbo提供的事件钩子,在页面/Frame更新后根据当前URL调整导航高亮状态,这是最贴合Turbo局部更新理念的方案。
- 修改导航HTML,给每个菜单项添加路径标识
<!-- navigation --> <nav class="nav font-semibold text-md"> <ul class="flex items-center"> <li class="p-4 border-b-2 border-blue-600 border-opacity-0 hover:border-opacity-100 hover:text-blue-600 duration-200 cursor-pointer" data-path="<%= posts_path %>"> <%= link_to 'Posts', posts_path, data: { turbo_frame: :main, turbo_action: 'replace' } %> </li> <li class="p-4 border-b-2 border-blue-600 border-opacity-0 hover:border-opacity-100 hover:text-blue-600 duration-200 cursor-pointer" data-path="<%= users_path %>"> <%= link_to 'Users', users_path, data: { turbo_frame: :main, turbo_action: 'replace' } %> </li> </ul> </nav>
- 添加JavaScript逻辑监听Turbo事件
// 监听Turbo页面加载完成事件 document.addEventListener('turbo:load', updateActiveNav); // 监听main Frame加载完成事件 document.addEventListener('turbo:frame-load', function(event) { if (event.target.id === 'main') { updateActiveNav(); } }); function updateActiveNav() { const currentPath = window.location.pathname; // 移除所有菜单项的高亮类 document.querySelectorAll('[data-path]').forEach(item => { item.classList.remove('border-opacity-100', 'text-blue-600'); }); // 给当前路径对应的菜单项添加高亮类 const activeItem = document.querySelector(`[data-path="${currentPath}"]`); if (activeItem) { activeItem.classList.add('border-opacity-100', 'text-blue-600'); } }
方法二:用Stimulus控制器封装高亮逻辑(更优实践)
将高亮逻辑封装为Stimulus控制器,更符合Rails现代前端代码组织规范:
- 创建Stimulus控制器
// app/javascript/controllers/active_nav_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { connect() { this.updateActive() this.setupListeners() } setupListeners() { document.addEventListener('turbo:load', () => this.updateActive()) document.addEventListener('turbo:frame-load', (e) => { if (e.target.id === 'main') this.updateActive() }) } updateActive() { const currentPath = window.location.pathname this.element.querySelectorAll('[data-path]').forEach(item => { const isActive = item.dataset.path === currentPath item.classList.toggle('border-opacity-100', isActive) item.classList.toggle('text-blue-600', isActive) }) } }
- 在导航中引用控制器
<nav class="nav font-semibold text-md" data-controller="active-nav"> <ul class="flex items-center"> <li class="p-4 border-b-2 border-blue-600 border-opacity-0 hover:border-opacity-100 hover:text-blue-600 duration-200 cursor-pointer" data-path="<%= posts_path %>"> <%= link_to 'Posts', posts_path, data: { turbo_frame: :main, turbo_action: 'replace' } %> </li> <li class="p-4 border-b-2 border-blue-600 border-opacity-0 hover:border-opacity-100 hover:text-blue-600 duration-200 cursor-pointer" data-path="<%= users_path %>"> <%= link_to 'Users', users_path, data: { turbo_frame: :main, turbo_action: 'replace' } %> </li> </ul> </nav>
不推荐的方案(供参考)
如果一定要用服务器端逻辑更新高亮,可以将导航放入独立的Turbo Frame,每次页面切换时同时更新导航Frame,但这会增加服务器请求量,性能不如前端方案高效。
内容的提问来源于stack exchange,提问作者Ahmed Gasanov
相关产品推荐
相关产品推荐

