You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在单页面触发「离开网站?」提示——Turbo & Stimulus JS实现

实现特定表单页面的原生离开提示(匹配StackOverflow效果)

需求

在/posts/new或/posts/xxx/edit路径的表单页面,用户离开前触发浏览器原生的「离开网站?」提示,效果与StackOverflow一致。

当前问题

  • 页面加载后,离开任意页面都会弹出提示,无法仅限制在目标表单页面
  • 通过Turbo触发的提示是自定义弹窗,不是浏览器原生样式

期望弹窗样式:浏览器原生离开提示弹窗
当前Turbo触发的弹窗样式:Turbo自定义离开提示弹窗

当前代码

Stimulus控制器代码

// javascript/controllers/unsaved_changes_controller.js
import { Controller } from "@hotwired/stimulus"

const alert_message = "Leave site? Changes you made may not be saved."

export default class extends Controller {

  connect() {
    window.addEventListener("beforeunload", (event) => {
      leavingPage(event);
    });  
    document.addEventListener('turbo:before-visit', (event) => {
      leavingPage(event);
    });
  }
  
}

function leavingPage(event) {
  console.log("leavingPage")
  if (event.type == "turbo:before-visit") {
    if (!window.confirm(alert_message)) { 
      event.preventDefault()
    }
  } else {
    event.returnValue = alert_message;
    return event.returnValue;
  }
}

视图代码

<form ... data-controller="unsaved-changes">

解决方案

核心问题分析

  1. 全局事件监听未在页面销毁时移除,导致离开表单页面后提示仍生效
  2. Turbo单页跳转不会触发浏览器原生beforeunload事件,只能用自定义弹窗

完整修改后的控制器代码

// javascript/controllers/unsaved_changes_controller.js
import { Controller } from "@hotwired/stimulus"

const alert_message = "Leave site? Changes you made may not be saved."

export default class extends Controller {
  connect() {
    // 保存表单初始值,用于判断是否有未保存更改
    this.initialFormData = new FormData(this.element);
    
    // 保存事件处理函数引用,方便销毁时移除
    this.beforeUnloadHandler = (event) => this.leavingPage(event);
    this.turboBeforeVisitHandler = (event) => this.leavingPage(event);
    
    window.addEventListener("beforeunload", this.beforeUnloadHandler);  
    document.addEventListener('turbo:before-visit', this.turboBeforeVisitHandler);
  }

  disconnect() {
    // 移除全局事件监听,确保离开表单页面后不再触发提示
    window.removeEventListener("beforeunload", this.beforeUnloadHandler);
    document.removeEventListener('turbo:before-visit', this.turboBeforeVisitHandler);
  }

  leavingPage(event) {
    // 只有表单有未保存更改时才触发提示
    if (!this.hasUnsavedChanges()) return;

    if (event.type === "turbo:before-visit") {
      // 阻止Turbo的单页跳转,改用原生跳转触发beforeunload提示
      event.preventDefault();
      window.location.href = event.detail.url;
    } else {
      // beforeunload事件:设置返回值触发原生提示
      event.returnValue = alert_message;
      return event.returnValue;
    }
  }

  // 判断表单是否有未保存更改
  hasUnsavedChanges() {
    const currentFormData = new FormData(this.element);
    
    // 比较初始值和当前值
    for (const [key, initialValue] of this.initialFormData.entries()) {
      const currentValue = currentFormData.get(key);
      if (currentValue !== initialValue) {
        return true;
      }
    }
    
    // 检查是否有新增的文件(处理文件上传场景)
    for (const [key] of currentFormData.entries()) {
      if (!this.initialFormData.has(key)) {
        return true;
      }
    }
    
    return false;
  }
}

额外优化:仅在目标路径加载控制器

通过视图条件判断,仅在目标路径的表单上添加控制器,避免不必要的初始化:

<% if request.path.match?(/\/posts\/new$|\/posts\/\d+\/edit$/) %>
  <form ... data-controller="unsaved-changes">
<% else %>
  <form ...>
<% end %>

内容的提问来源于stack exchange,提问作者Kobius

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 11:54:14