Turbo 路由是 PJAX (push state 和 AJAX) 的一种实现,它提供了单页应用的性能优势,同时避免了客户端框架的额外复杂性。当您点击链接时,页面会在客户端自动替换,无需承担完整页面加载的开销。
{% framework turbo %}你可以通过以下方式编程式地访问链接。
oc.visit(location);替换当前 URL,但不将其添加到导航历史记录中,类似于 window.history.replaceState,将 action 选项设置为 replace。
oc.visit(location, { action: 'replace' });检查 turbo 路由器是否已启用并应使用。
if (oc.useTurbo && oc.useTurbo()) {
// Use PJAX
}要禁用特定页面上的 PJAX 路由,您可以通过在页面的 head 部分包含 turbo-visit-control meta 标签,并将其值设置为 reload,来触发一次完全重新加载。这将仅针对传入请求禁用此功能。
<head>
<meta name="turbo-visit-control" content="reload" />
</head>要在您的网站中完全禁用 PJAX,请将值设置为 disable。这将禁用此功能,适用于所有入站和出站请求。
<meta name="turbo-visit-control" content="disable" />默认情况下,所有内部 HTML 链接将使用 PJAX 进行路由,但您可以通过用 data-turbo="false" 标记链接或其父容器来禁用此功能。被禁用的链接将由浏览器正常处理。
<a href="/" data-turbo="false">Disabled</a>你可以在其祖先已禁用时重新启用:
<div data-turbo="false">
<a href="/" data-turbo="true">Enabled</a>
</div>每次访问都会滚动到页面顶部,就像浏览器中的大多数链接一样。然而,在某些情况下,保留滚动位置很有用,例如链接充当筛选器的情况。您可以使用链接元素上的 data-turbo-no-scroll 属性禁用访问滚动。
<a href="/" data-turbo-no-scroll>Filter</a>在某些情况下,您可能希望在页面上包含静态元素,这些元素在页面更新时不应被刷新。在父元素上使用 data-turbo-permanent 属性。该元素还必须提供一个 id 属性,以便将原始页面与新页面匹配,包括事件监听器。
<div id="main-navigation" data-turbo-permanent>...</div>默认情况下,PJAX 用于同一域名下的所有链接,并且访问任何其他 URL 将回退到完整的页面加载。在某些情况下,您的应用程序可能位于子目录中,并且链接应仅适用于根路径。
例如,如果您的网站位于 /app 并且您不希望链接应用于 /docs 中的不同站点 那么您可以将链接限制在根路径。您可以通过在页面头部包含 turbo-root meta 标签来设置根路径。
<head>
<meta name="turbo-root" content="/app">
</head>当使用 PJAX 并且服务器响应错误代码时,例如 404 或 500 状态码,完整的 html 元素会被替换,包括脚本和样式表。这可以防止意外地用非同一应用程序代码生成的内容替换 body 元素。
您可以通过在页面的 head 部分包含 turbo-visit-control meta 标签并将其值设置为 error 来禁用此行为。这将告知 turbo 路由器,错误页面内容是由原生应用程序生成的。
<meta name="turbo-visit-control" content="error">启用缓存后,Turbo 路由器通过显示重新访问的页面而无需访问网络来提升网站性能,使网站感觉更快。点击链接时,内容会从浏览器的本地存储中显示,同时页面在后台发起请求。当网络请求完成后,会显示最新页面,这意味着页面会渲染两次。
如果需要在文档进入缓存前对其进行准备,您可以监听 page:before-cache 事件。您可以将其用于重置表单、折叠 UI 元素或卸载任何第三方控件等操作,以便页面准备好再次显示。
addEventListener('page:before-cache', function() {
// Close any open submenus, etc.
});你可以通过 HTML 元素上的 data-turbo-preview 属性,检测页面内容何时从缓存中获取。在 JavaScript 中表示如下。
if (document.documentElement.hasAttribute('data-turbo-preview')) {
// Page shown is loaded from cache
}或者使用带有以下内容的样式表。
html[data-turbo-preview] {
/* Hide overlays from previous view */
}您可以通过在页面的 head 部分使用 turbo-cache-control meta 标签来禁用单个页面的页面缓存。将此值设置为 no-cache 将完全禁用缓存。您还可以将此设置为 no-preview 以在使用浏览器的“后退”和“前进”按钮导航时保留缓存版本。
<head>
<meta name="turbo-cache-control" content="no-cache">
</head>当使用 PJAX 时,页面内容可能会动态加载,这与常见的浏览器行为不同。为解决此问题,使用 render 事件处理程序会在每次页面加载时被调用,包括 AJAX 更新。
addEventListener('render', function() {
// Page has rendered something new
});oc.pageReady 函数用于在页面和脚本准备就绪时调用代码。该函数返回一个 promise,它在所有页面脚本加载完成后解析,或者如果它们已加载,则立即解析。
oc.pageReady().then(() => {
// Page has finished loading scripts
});oc.waitFor 是另一个有用的函数,它将等待一个对象或变量存在。该函数返回一个 promise,当变量被找到时解析。
oc.waitFor(() => window.propName).then(() => [
// window.propName is now available
]);第二个参数提供一个以毫秒为单位的超时间隔,后续操作将在两秒后停止等待。
oc.waitFor(() => window.propName, 2000).then(() => {
console.log('Found the variable!')
}).catch(() => {
console.error('Gave up waiting...')
});turbo 路由器通过比较差异来维护页面 <head> 标签内的脚本。如果您在 <body> 标签中使用 script 标签,那么每次页面渲染时脚本都会执行,这可能是不希望发生的。
您可以包含 data-turbo-eval="false" 以仅允许脚本在首次页面加载时执行。脚本将不会在任何 PJAX 请求中被调用。
<body>
<script data-turbo-eval="false" src="{{ ['@framework.bundle']|theme }}"></script>
</body>如果你出于性能原因将脚本放置在
<body>标签中,考虑将其移至<head>标签并使用<script defer>代替。
:::
若要仅执行一次内联 JavaScript 代码,无论首次页面加载还是 PJAX 请求,请为 data-turbo-eval-once 属性设置一个唯一值。此唯一值 (例如 myAjaxPromise) 用于确定脚本是否已被执行过。
<script data-turbo-eval-once="myAjaxPromise">
// This script will run once only
addEventListener('ajax:promise', function(event) {
//
});
</script>October CMS 提供了一个配套库,用于简化构建幂等控件的实现。
当页面访问发生且 JavaScript 组件被初始化时,重要的是这些函数是幂等的。简单来说,幂等函数可以安全地多次应用,而不会改变其初次应用之后的结果。
使函数幂等的一种技术是,通过在每个已处理的元素上向 dataset 属性添加一个值来跟踪你是否已执行过它。这对于外部脚本很有用。
addEventListener('page:loaded', function() {
// Find my control
var myControl = document.querySelector('.my-control');
// Check if control has already been initialized
if (!myControl.dataset.hasMyControl) {
myControl.dataset.hasMyControl = true;
// Initialize since this is the first time
initializeMyControl(myControl);
}
});作为一般性建议,一个更简单的方法是允许函数运行多次,并在内部应用幂等技术。例如,首先检查菜单分隔符是否已存在,然后再创建新的。
在某些情况下,您可能仅针对特定页面绑定全局事件,例如,将热键绑定到某个动作。
addEventListener('keydown', myKeyDownFunction);为了防止此事件泄露到其他页面,你应该使用 page:unload 方法移除此事件,它将销毁任何事件和控件。此事件可以使用一次来安全地处置控件和事件。
addEventListener('page:unload', function() {
removeEventListener('keydown', myKeyDownFunction);
}, { once: true });October CMS 包含一个附赠的库,用于 构建一次性控件。
如果您想在加载新页面之前对某些元素(例如下拉菜单或抽屉菜单)进行动画处理,您可以通过阻止默认行为来暂停 page:before-render 事件,并使用事件详情中的 resume() 函数恢复它。
addEventListener('page:before-render', async (event) => {
event.preventDefault();
await animateOut();
event.detail.resume();
});请记住,page:before-render 事件可能会触发两次,一次来自缓存,另一次在请求新的页面内容之后。
AJAX 框架在导航生命周期和页面响应期间触发多个事件。这些事件通常在 document 对象上触发,其详细信息可在 event.detail 属性中获取。
| Event | Description |
|---|---|
| render | triggered when the page updates via PJAX or AJAX. |
| page:click | triggered when a turbo-routed link is clicked. |
| page:before-visit | triggered before visiting a location, except when navigating using browser history. |
| page:visit | triggered after a clicked visit starts. |
| page:request-start | triggered before the request for a page. |
| page:request-end | triggered after the page request ends. |
| page:before-cache | triggered before a page is cached. |
| page:before-render | triggered before the page content is rendered. |
| page:render | triggered after the page is rendered. This is fired twice, once from cache and once again after requesting the new page content. |
| page:load | triggered once after the initial page load and again every time a page is visited. |
| page:loaded | identical to page:load except will wait for all newly added scripts to load. |
| page:updated | similar to DOMContentLoaded except triggered only when a page is visited. |
| page:unload | called when a previously loaded page should be disposed. |
以下 JavaScript 将会在每次页面加载时运行,包括脚本在内。
addEventListener('page:loaded', function() {
// ...
});在某些情况下,当您使用热重载或浏览器同步技术开发网站时,turbo 路由可能会产生干扰,例如,在使用 laravel-mix & browsersync 的开发模式下与 Laravel Mix 配合使用时。为了解决这个问题,请将以下代码添加到您的 webpack browserSync 配置中。
snippetOptions: {
rule: {
match: /<\/head>/i,
fn: function (snippet, match) {
return '<meta name="turbo-visit-control" content="disable" />';
}
}
}