使用 Swup 实现网页平滑过渡
Swup 用于服务器呈现网站的多功能且可扩展的页面过渡库。
缘起
最近看到 Astro ,感觉不错。试了试,虽然感觉生成速度很慢,但是工具链确实很现代化。比如支持 MDX,生成后进行文件压缩,图片转 WebP,少量 js 实现的页面过渡等等功能。
其中比较喜欢 Astro Paper 和 Fuwari 主题,这两个都有类似 SPA 应用效果,Paper 使用的应该是 Astro4 自带的 API。而 Fuwari,使用的是 Swup 实现的。 Fuwari Demo
下面进行简单尝试。
信息
官网介绍
Swup 是一个多功能且可扩展的页面过渡库,适用于 SSR 网站。 它管理整个页面加载生命周期,并在当前和下一个之间平滑地制作动画页。此外,它还提供了许多其他生命周期改进,例如缓存、智能预加载、 本机浏览器历史记录和增强的可访问性。无需复杂操作,即可让您的网站感觉像一个 SPA 单页应用程序。
虽然适用于 SSR 网站,但静态博客也能用。
示例
以本博客站点为例, layouts/_default/baseof.html 添加
<script src="https://unpkg.com/swup@4"></script>
<script src="https://unpkg.com/@swup/scripts-plugin@2"></script>
<script data-swup-ignore-script>
const swup = new Swup({
plugins: [new SwupScriptsPlugin({
body: true,
head: true
})]
});
</script>
这段代码引入了 swup 主要 js 和一个插件。插件主要作用是保证 body ,head js 能够正常运行。(但不保证完全正常)
data-swup-ignore-script
是告诉插件 SwupScriptsPlugin
这段 js 不必重复执行。
除此之外,还需要根据错误,适当修改代码。比如把需要重新执行 js 放到 body ,head 或 main 里。总的来说,增加了页面维护难度。
比如 DoIt 这个主题,我把 {{- partial "assets.html" . -}}
放到 <main></main>
里,评论有时还不会生效。
main 增加 transition-fade
class 和 id swup
。
<main class="main transition-fade" id="swup">
css 增加
html.is-changing .transition-fade {
transition: opacity 0.1s;
opacity: 1;
}
/* Define the styles for the unloaded pages */
html.is-animating .transition-fade {
opacity: 0;
}
忽略控制台报错和一些无法正常工作的脚本还是不错的。按照 4.5.1 版本来看, https://cdn.staticfile.net/swup/4.5.1/Swup.umd.min.js 文件,这个 js 大概 10kb ,未压缩 25kb 左右。插件未压缩不到 3kb 。
类似工具
http://instantclick.io/ 预加载,访问体验优化。并使用 pushState 和 Ajax (pjax) 替换正文。可能会导致部分 js 失效,增加维护难度。5.9kb
https://instant.page/ 预加载缓存 4.2kb
附件
baseof.html 完整代码参考
{{- partial "init.html" . -}}
<!DOCTYPE html>
<html lang="{{ .Site.LanguageCode }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noodp" />
{{- /* Paginate */ -}}
{{- /* Paginate in here, To solve the problem of the canonical URL being the same in the pagination */ -}}
{{- /* see more https://github.com/gohugoio/hugo/issues/4507 */ -}}
{{- /* see more https://discourse.gohugo.io/t/control-pagination-and-page-collections-from-baseof-html/37643/8 */ -}}
{{- /* see more https://discourse.gohugo.io/t/determine-if-current-page-is-result-of-pagination/37494/4 */ -}}
{{- partial "head/paginator.html" . -}}
<title>
{{- block "title" . }}{{ .Site.Title }}{{ end -}}
</title>
{{- partial "head/meta.html" . -}}
{{- partial "head/link.html" . -}}
{{- partial "head/seo.html" . -}}
{{- $instantpage := .Scratch.Get "instantpage" | default dict -}}
{{- if $instantpage.enable -}}
<script src="//instant.page/5.2.0" defer type="module" integrity="sha384-jnZyxPjiipYXnSU0ygqeac2q7CVYMbh84q0uHVRRxEtvFPiQYbXWUorga2aqZJ0z"></script>
{{- end -}}
{{- $instantpage := .Site.Params.page.instantpage.enable -}}
{{- if $instantpage -}}
{{- $js := resources.Get "/lib/instant.page/instantpage.min.js" -}}
<script
src="{{ $js.RelPermalink }}"
defer
type="module"
></script>
{{- end -}}
<script src="https://cdn.staticfile.net/swup/4.5.1/Swup.umd.min.js"></script>
<script src="https://unpkg.com/@swup/scripts-plugin@2"></script>
<script data-swup-ignore-script>
const swup = new Swup({
plugins: [new SwupScriptsPlugin({
body: true,
head: true
})]
});
document.addEventListener('DOMContentLoaded', () => {
console.log("Swup Running");
});
</script>
</head>
<body header-desktop="{{ .Site.Params.header.desktopMode }}" header-mobile="{{ .Site.Params.header.mobileMode }}">
{{- /* Check theme isDark before body rendering */ -}}
{{- $theme := .Site.Params.defaulttheme -}}
<script data-swup-ignore-script type="text/javascript">
function setTheme(theme) {
document.body.setAttribute('theme', theme);
document.documentElement.style.setProperty('color-scheme', theme === 'light' ? 'light' : 'dark');
if (theme === 'light') {
document.documentElement.classList.remove('tw-dark')
} else {
document.documentElement.classList.add('tw-dark')
}
window.theme = theme;
window.isDark = window.theme !== 'light'
}
function saveTheme(theme) {window.localStorage && localStorage.setItem('theme', theme);}
function getMeta(metaName) {const metas = document.getElementsByTagName('meta'); for (let i = 0; i < metas.length; i++) if (metas[i].getAttribute('name') === metaName) return metas[i]; return '';}
if (window.localStorage && localStorage.getItem('theme')) {let theme = localStorage.getItem('theme');theme === 'light' || theme === 'dark' || theme === 'black' ? setTheme(theme) : (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? setTheme('dark') : setTheme('light')); } else { if ('{{ $theme }}' === 'light' || '{{ $theme }}' === 'dark' || '{{ $theme }}' === 'black') setTheme('{{ $theme }}'), saveTheme('{{ $theme }}'); else saveTheme('auto'), window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? setTheme('dark') : setTheme('light');}
let metaColors = {'light': '#f8f8f8','dark': '#252627','black': '#000000'}
getMeta('theme-color').content = metaColors[document.body.getAttribute('theme')];
window.switchThemeEventSet = new Set()
</script>
<div id="back-to-top"></div>
<div id="mask"></div>
{{- /* Body wrapper */ -}}
<div class="wrapper">
{{- partial "header.html" . -}}
<main class="main transition-fade" id="swup">
<div class="container">
{{- block "content" . }}{{ end -}}
</div>
{{- /* Load JavaScript scripts and CSS */ -}}
{{- partial "assets.html" . -}}
</main>
{{- partial "footer.html" . -}}
</div>
<div id="fixed-buttons" class="print:!tw-hidden">
{{- /* top button */ -}}
<a href="#back-to-top" id="back-to-top-button" class="fixed-button" title="{{ T `backToTop` }}">
{{ partial "plugin/fontawesome.html" (dict "Style" "solid" "Icon" "arrow-up") }}
</a>
{{- /* comment button */ -}}
<a href="#" id="view-comments" class="fixed-button" title="{{ T `viewComments` }}">
{{ partial "plugin/fontawesome.html" (dict "Style" "solid" "Icon" "comment") }}
</a>
</div>
</body>
</html>
后续
因为我目前使用的 DoIt 主题 JS 写的有点复杂,加上 Swup 会导致部分 JS 失效,就删除了。
Swup.js 效果可见
https://note.ftls.xyz/m5/
https://archived.ftls.xyz/hugo-m3/
https://kkbt0.github.io/Hugo-Landscape/
欢迎赞赏~
赞赏