Rails7中如何用Turbo Stream实现无自定义JS的文件上传进度显示?
问题
我有一个Rails 7的表单:
<%= form_with(model: doc, url: my_docs_path(@application, doc), id: "#{dom_id(doc)}_form", html: {multipart: true}) do |form| %> <%= form.file_field :doc %> <%= form.submit 'Upload Doc' %> <% end %>
该表单可正常提交至DocsController#create,控制器中配置了format.turbo_stream,create.turbo_stream.erb运行良好,上传后无需自定义JS和整页刷新即可显示新文档记录。但大文件上传时用户会觉得页面“卡住”,想要实现类似以下代码的上传进度逻辑,但不想使用这段手动编写XHR的JS:
<form autocomplete="off" enctype="multipart/form-data"> <br/> <input type="file" name="file" id="f1" accept="video/*" onchange="uploadFile('/file/{{.name}}')"/> <span id="p">---</span> <br/> </form> <script type="text/javascript"> window.uploadFile = function(url){ var formData = new FormData(); var fileInputElement = document.getElementById("f1"); formData.append("file", fileInputElement.files[0]); var xhr = new XMLHttpRequest(); xhr.open("POST", url); xhr.upload.onprogress = function (e) { if (e.lengthComputable) { document.getElementById("p").innerHTML = ""+((e.loaded/e.total)*100); } } xhr.upload.onloadstart = function (e) { document.getElementById("p").innerHTML = "0"; } xhr.upload.onloadend = function (e) { document.getElementById("p").innerHTML = ""+e.loaded; document.location.href = '/'; } xhr.send(formData); } </script>
请问:在Rails7中,不使用上述手动XHR的JS代码,能否通过xhr.upload.onprogress事件向用户显示上传进度百分比?
解答
可以实现,无需完全重写XHR上传逻辑,只需借助Turbo的事件钩子监听内置XHR的上传进度,步骤如下:
- 添加进度显示元素
在表单内新增用于展示进度的元素,比如百分比文本和进度条:
<%= form_with(model: doc, url: my_docs_path(@application, doc), id: "#{dom_id(doc)}_form", html: {multipart: true}) do |form| %> <%= form.file_field :doc %> <div id="upload-progress" style="display: none; margin-top: 8px;"> <span id="progress-percentage">0%</span> <div style="width: 0%; height: 4px; background: #007bff; transition: width 0.2s ease; margin-top: 4px;"></div> </div> <%= form.submit 'Upload Doc' %> <% end %>
- 绑定Turbo的XHR事件监听
Turbo处理表单提交时会自动创建XHR对象,我们可以通过turbo:before-fetch-request事件获取该对象,然后绑定upload.onprogress相关回调:
document.addEventListener('turbo:before-fetch-request', (event) => { const { xhr } = event.detail; const form = event.target; // 仅针对目标文件上传表单生效 if (form.id === "<%= dom_id(doc) %>_form" && form.enctype === 'multipart/form-data') { const progressContainer = form.querySelector('#upload-progress'); const progressText = form.querySelector('#progress-percentage'); const progressBar = progressContainer.querySelector('div'); xhr.upload.onloadstart = () => { progressContainer.style.display = 'block'; progressText.textContent = '0%'; progressBar.style.width = '0%'; }; xhr.upload.onprogress = (e) => { if (e.lengthComputable) { const percent = Math.round((e.loaded / e.total) * 100); progressText.textContent = `${percent}%`; progressBar.style.width = `${percent}%`; } }; xhr.upload.onloadend = () => { // 可选:上传完成后延迟隐藏进度条,给用户反馈 setTimeout(() => { progressContainer.style.display = 'none'; }, 1000); }; } });
- 关键注意事项
- 这段JS可放在表单所在页面的
<script>标签中,或打包到应用全局JS文件里 - 确保Rails 7默认启用的Turbo已正常加载
- 若需适配多个表单,可将ID判断改为类名判断,提升通用性
这种方式既保留了Turbo原本的turbo_stream响应优势,又无需手动编写完整XHR逻辑,即可实现上传进度的实时展示。
内容的提问来源于stack exchange,提问作者Andrew Arrow
相关产品推荐
相关产品推荐

