如何在Ruby on Rails的html.erb中调用JS函数并传递Ruby变量
Rails 活动倒计时实现:JS 传参踩坑与最终方案
原写法的失效原因
- 内联脚本直接嵌入Ruby变量的写法
<%= javascript_tag "dynamicCountdown(@event.date_time)" %>存在核心缺陷:- Rails 渲染后输出的JS代码会变成
dynamicCountdown(2024-05-20 14:30:00 UTC),这在JS语法里是完全非法的:没有加引号的时间字符串会被解析为多个减号连接的数字表达式,直接触发语法报错 - 就算手动给值加引号解决了语法问题,原JS中
setInterval(dynamicCountdown, 1000)调用函数时根本没有传入参数,函数接收到的eventTime永远是undefined,无法正常计算剩余时间
- Rails 渲染后输出的JS代码会变成
- 直接硬塞Ruby值到内联JS的写法,还存在XSS注入风险,不符合安全规范;如果项目开启了严格的内容安全策略(CSP),内联脚本会被直接拦截,无法执行。
最终可落地的实现方案
用HTML标准的自定义data属性传递后端数据,是Rails场景下前后端交互的通用可靠方案,具体分两步实现:
- 在erb模板中给DOM元素绑定data属性存储活动时间
一定要把时间转成UTC的ISO 8601格式,避免不同用户的本地时区导致倒计时计算错乱,示例代码:<h3 class="timer" data-event-date="<%= @event.date_time.utc.iso8601 %>"></h3> - 修改JS逻辑,从DOM属性读取时间值完成倒计时计算
调整后的可运行JS代码:let countdown; const timerEl = document.querySelector(".timer"); // 从data属性读取标准格式的活动时间值 const eventTime = timerEl?.dataset.eventDate; const dynamicCountdown = () => { if (!timerEl || !eventTime) return; const timeRemaining = Date.parse(eventTime) - Date.parse(new Date()); // 活动时间到点后停止计时 if (timeRemaining < 0) { clearInterval(countdown); timerEl.textContent = "活动已开始"; return; } const hours = Math.floor(timeRemaining / (1000 * 60 * 60) % 24); const minutes = Math.floor((timeRemaining / 1000 / 60) % 60); const seconds = Math.floor((timeRemaining / 1000) % 60); const display = `${hours < 10 ? '0' : ''}${hours}:${minutes < 10 ? '0' : ''}${minutes}:${seconds < 10 ? '0' : ''}${seconds}`; timerEl.textContent = display; } // 页面加载后立即计算一次初始值,再开启每秒刷新的定时器 dynamicCountdown(); countdown = setInterval(dynamicCountdown, 1000); export { dynamicCountdown }
注意:不要直接把未格式化的Ruby Time对象值直接放到data属性或JS代码中,必须通过
iso8601、strftime等方法格式化为JS可识别的标准时间字符串,否则会出现解析失败、时区计算错误的问题。
内容的提问来源于stack exchange,提问作者raphael-allard
相关产品推荐
相关产品推荐

