Rails 7中使用jQuery事件监听器时出现错误求助
Rails7 Turbo环境下JS错误排查与解决
问题场景
在Rails7中通过CDN在_head.html.erb的<head>标签引入jQuery:
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
随后在show.html.erb中添加轮播相关jQuery代码:
<script> var carouselWidth = $('.carousel-inner')[0].scrollWidth; var cardWidth = $('.carousel-item').width(); var scrollPosition = 0; $('.carousel-control-next').on('click', function(){ console.log('next'); scrollPosition = scrollPosition + cardWidth; $('.carousel-inner').animate({scrollLeft: scrollPosition}, 600); }); </script>
浏览器控制台抛出错误:
menu-f19d6842a068966109ba6a8b3de0283869f632ec673afe222888bbeba8442401.js:7 Uncaught TypeError: Cannot read properties of null (reading 'addEventListener') at addToggleListener (menu-f19d6842a068966109ba6a8b3de0283869f632ec673afe222888bbeba8442401.js:7:26) at HTMLDocument.<anonymous> (menu-f19d6842a068966109ba6a8b3de0283869f632ec673afe222888bbeba8442401.js:15:9) at S (turbo.es2017-esm.js:344:34) at Object.notifyApplicationAfterPageLoad (turbo.es2017-esm.js:3158:16) at Object.pageBecameInteractive (turbo.es2017-esm.js:3080:14) at he.pageIsInteractive (turbo.es2017-esm.js:2486:27) at he.pageIsComplete (turbo.es2017-esm.js:2490:14) at HTMLDocument.interpretReadyState (turbo.es2017-esm.js:2458:22) addToggleListener @ menu-f19d6842a068966109ba6a8b3de0283869f632ec673afe222888bbeba8442401.js:7 (anonymous) @ menu-f19d6842a068966109ba6a8b3de0283869f632ec673afe222888bbeba8442401.js:15 S @ turbo.es2017-esm.js:344 notifyApplicationAfterPageLoad @ turbo.es2017-esm.js:3158 pageBecameInteractive @ turbo.es2017-esm.js:3080 pageIsInteractive @ turbo.es2017-esm.js:2486 pageIsComplete @ turbo.es2017-esm.js:2490 interpretReadyState @ turbo.es2017-esm.js:2458
补充menu.js内容:
function addToggleListener(selected_id, menu_id, toggle_class) { let selected_element = document.querySelector(`#${selected_id}`); selected_element.addEventListener('click', function(event){ event.preventDefault(); let menu = document.querySelector(`#${menu_id}`) menu.classList.toggle(toggle_class); }); } document.addEventListener('turbo:load', function(){ addToggleListener('hamburger', 'navbar-menu', 'collapse'); addToggleListener('account', 'dropdown-menu', 'active'); });
错误原因
核心问题是:document.querySelector(#${selected_id})返回null,即当前页面不存在对应ID的元素,但代码直接调用了addEventListener方法。
在Rails7默认启用Turbo的环境下,turbo:load事件会在每次页面加载(包括Turbo驱动的跳转)时触发。如果当前访问的页面(比如show.html.erb)没有#hamburger或#account元素,调用addToggleListener就会触发该错误。
同时,轮播的jQuery代码也存在潜在问题:直接写在页面中的JS会在DOM未完全渲染时执行,可能导致无法获取.carousel-inner等元素。
解决方案
1. 修复menu.js的空值判断
修改addToggleListener函数,先检查元素是否存在再绑定事件:
function addToggleListener(selected_id, menu_id, toggle_class) { let selected_element = document.querySelector(`#${selected_id}`); // 先判断触发元素是否存在 if (!selected_element) return; selected_element.addEventListener('click', function(event){ event.preventDefault(); let menu = document.querySelector(`#${menu_id}`); // 再判断菜单元素是否存在 if (!menu) return; menu.classList.toggle(toggle_class); }); } document.addEventListener('turbo:load', function(){ addToggleListener('hamburger', 'navbar-menu', 'collapse'); addToggleListener('account', 'dropdown-menu', 'active'); });
2. 优化轮播jQuery代码的执行时机
由于jQuery放在<head>中,DOM未渲染完成时JS就会执行,需要将代码包裹在turbo:load事件或jQuery的ready方法中:
<script> document.addEventListener('turbo:load', function() { var carouselInner = $('.carousel-inner')[0]; if (!carouselInner) return; var carouselWidth = carouselInner.scrollWidth; var cardWidth = $('.carousel-item').width(); var scrollPosition = 0; $('.carousel-control-next').on('click', function(){ console.log('next'); scrollPosition = scrollPosition + cardWidth; $('.carousel-inner').animate({scrollLeft: scrollPosition}, 600); }); }); </script>
或使用jQuery原生写法:
<script> $(document).ready(function() { var carouselInner = $('.carousel-inner')[0]; if (!carouselInner) return; var carouselWidth = carouselInner.scrollWidth; var cardWidth = $('.carousel-item').width(); var scrollPosition = 0; $('.carousel-control-next').on('click', function(){ console.log('next'); scrollPosition = scrollPosition + cardWidth; $('.carousel-inner').animate({scrollLeft: scrollPosition}, 600); }); }); </script>
3. jQuery代码的其他存放方式
- 多页面共用代码:可放在
app/javascript/controllers/目录作为Turbo控制器,或写入app/javascript/application.js(需确保jQuery已加载)。 - 页面特定代码:除直接写在
show.html.erb外,还可通过content_for在页面中引入独立JS文件,或使用Turbo的页面特定加载逻辑。
内容的提问来源于stack exchange,提问作者John Sall
相关产品推荐
相关产品推荐

