某些 API 可能需要較長回應時間,實務上會加上 Spinner 視覺效果,這在 Tailwind CSS 該如何實現呢 ?
Version
Tailwind CSS 2.1.1
Composition API
按下 Get Books
後,會先顯示 spinner。
等 API 完全回傳後才顯示資料。
<template lang='pug'>
div(v-if='isShowSpinner').fixed.top-0.left-0.right-0.bottom-0.w-full.h-screen.z-50.overflow-hidden.bg-gray-700.opacity-75.flex.flex-col.items-center.justify-center
svg(xmlns='http://www.w3.org/2000/svg', viewBox='0 0 16 16').w-16.h-16.animate-spin
path(fill='#ffffff',d='M12.9 3.1c1.3 1.2 2.1 3 2.1 4.9 0 3.9-3.1 7-7 7s-7-3.1-7-7c0-1.9 0.8-3.7 2.1-4.9l-0.8-0.8c-1.4 1.5-2.3 3.5-2.3 5.7 0 4.4 3.6 8 8 8s8-3.6 8-8c0-2.2-0.9-4.2-2.3-5.7l-0.8 0.8z')
div
button(@click='onClick', type='button', class='px-2.5 py-1.5').border.border-gray-300.shadow-sm.text-xs.font-medium.text-gray-700.rounded.bg-white.hover_bg-gray-50.focus_outline-none.focus_ring-2.focus_ring-offset-2.focus_ring-indigo-500 Get Books
div
ul
li(v-for='x in books') {{ x.title }} / {{ x.price }}
</template>
<script setup>
import getBooks from '/src/api/getBooks'
ref: isShowSpinner = false
ref: books = []
let onClick = () => {
isShowSpinner = true
getBooks()
.then(x => (isShowSpinner = false, x))
.then(x => x.data)
.then(x => books = x)
}
</script>
第 2 行
div(v-if='isShowSpinner').fixed.top-0.left-0.right-0.bottom-0.w-full.h-screen.z-50.overflow-hidden.bg-gray-700.opacity-75.flex.flex-col.items-center.justify-center
svg(xmlns='http://www.w3.org/2000/svg', viewBox='0 0 16 16').w-16.h-16.animate-spin
path(fill='#ffffff',d='M12.9 3.1c1.3 1.2 2.1 3 2.1 4.9 0 3.9-3.1 7-7 7s-7-3.1-7-7c0-1.9 0.8-3.7 2.1-4.9l-0.8-0.8c-1.4 1.5-2.3 3.5-2.3 5.7 0 4.4 3.6 8 8 8s8-3.6 8-8c0-2.2-0.9-4.2-2.3-5.7l-0.8 0.8z')
由 div
與 svg
構成 spinner。
第 2 行
div(v-if='isShowSpinner').fixed.top-0.left-0.right-0.bottom-0.w-full.h-screen.z-50.overflow-hidden.bg-gray-700.opacity-75.flex.flex-col.items-center.justify-center
- 使用
isShowSpinner
state 控制 spinner 是否顯示 div
主要負責 overlay 部分
第 3 行
svg(xmlns='http://www.w3.org/2000/svg', viewBox='0 0 16 16').w-16.h-16.animate-spin
path(fill='#ffffff',d='M12.9 3.1c1.3 1.2 2.1 3 2.1 4.9 0 3.9-3.1 7-7 7s-7-3.1-7-7c0-1.9 0.8-3.7 2.1-4.9l-0.8-0.8c-1.4 1.5-2.3 3.5-2.3 5.7 0 4.4 3.6 8 8 8s8-3.6 8-8c0-2.2-0.9-4.2-2.3-5.7l-0.8 0.8z')
使用 svg 為 spinner:
w-16
:設定 spinner 寬度h-16
:設定 spinner 高度animate-spin
:使靜態 svg 能旋轉
18 行
let onClick = () => {
isShowSpinner = true
getBooks()
.then(x => (isShowSpinner = false, x))
.then(x => x.data)
.then(x => books = x)
}
- 一開始先設定
isShowSpinner
為true
顯示 spinner - 使用
getBooks()
呼叫 API,當 Promise 回傳成功立即設定isShowSpinner
為false
隱藏 spinner
Point-free
結果不變,但使用 Point-free 改寫。
<template lang='pug'>
div(v-if='isShowSpinner').fixed.top-0.left-0.right-0.bottom-0.w-full.h-screen.z-50.overflow-hidden.bg-gray-700.opacity-75.flex.flex-col.items-center.justify-center
svg(xmlns='http://www.w3.org/2000/svg', viewBox='0 0 16 16').w-16.h-16.animate-spin
path(fill='#ffffff',d='M12.9 3.1c1.3 1.2 2.1 3 2.1 4.9 0 3.9-3.1 7-7 7s-7-3.1-7-7c0-1.9 0.8-3.7 2.1-4.9l-0.8-0.8c-1.4 1.5-2.3 3.5-2.3 5.7 0 4.4 3.6 8 8 8s8-3.6 8-8c0-2.2-0.9-4.2-2.3-5.7l-0.8 0.8z')
div
button(@click='onClick', type='button', class='px-2.5 py-1.5').border.border-gray-300.shadow-sm.text-xs.font-medium.text-gray-700.rounded.bg-white.hover_bg-gray-50.focus_outline-none.focus_ring-2.focus_ring-offset-2.focus_ring-indigo-500 Get Books
div
ul
li(v-for='x in books') {{ x.title }} / {{ x.price }}
</template>
<script setup>
import { ref } from 'vue'
import { write } from 'vue3-fp'
import { pipe, andThen as then, prop, tap, T, F } from 'ramda'
import getBooks from '/src/api/getBooks'
let isShowSpinner = ref(false)
let books = ref([])
let showSpinner = pipe(
T,
write(isShowSpinner)
)
let hideSpinner = pipe(
F,
write(isShowSpinner)
)
let onClick = pipe(
showSpinner,
getBooks,
tap(then(hideSpinner)),
then(prop('data')),
then(write(books))
)
</script>
31 行
let onClick = pipe(
showSpinner,
getBooks,
tap(then(hideSpinner)),
then(prop('data')),
then(write(books))
)
使用 pipe()
組合 onClick()
:
showSpinner
:顯示 spinnergetBooks
:呼叫 APItap(then(hideSpinner))
:回傳成功則顯示 spinnerthen(prop('data'))
:只取data
propertythen(write(books))
:將結果寫入books
state
21 行
let showSpinner = pipe(
T,
write(isShowSpinner)
)
使用 pipe()
組合 showSpinner()
:
T
:準備true
write(isShowSpinner)
:寫入isShowSpinner
state
26 行
let hideSpinner = pipe(
F,
write(isShowSpinner)
)
使用 pipe()
組合 hideSpinner()
:
F
:準備false
write(isShowSpinner)
:寫入isShowSpinner
state
Conclusion
- Tailwind CSS 的
animate-spin
是關鍵,他使得靜態 svg 能旋轉達到動態效果,再搭配isShowSpinner
state 就能控制是否顯示 spinner