前兩天有給大家分享一個 svelte自定義Tabbar+Navbar元件。
今天給大家帶來的是最新開發的svelte自定義手機端模態框元件SveltePopup。
如下圖:在lib目錄下新建一個Popup元件目錄。
引入svelte-popup元件
彈窗元件支援元件式(Popup
)+函式式(svPopup
)兩種呼叫方式。
import Popup, {svPopup} from '$lib/Popup'
- 元件式呼叫
<Popup
bind:open={isShowDialog}
xclose
shadeClose="false"
title="標題"
content="顯示內容資訊"
btns={[
{text: '確認', style: 'color:#f60;', click: () => isShowDialog=false},
]}
on:open={handleOpen}
on:close={handleClose}
>
<svelte:fragment slot="content"><h3>自定義slot插槽顯示內容</h3></svelte:fragment>
</Popup>
- 函式式呼叫
let el = svPopup({
title: '標題資訊',
content: '<p style='color:#09f;'>展示內容資訊</p>',
xclose: true,
shadeClose: false,
btns: [
{text: '取消', click: () => { el.$set({open: false}) }},
{text: '確認', style: 'color:#f90;', click: () => handleOK},
],
onOpen: () => {},
onClose: () => {}
})
一些簡單的彈窗效果通過函式呼叫更加方便,對於一些功能豐富的彈窗展示,可以使用元件式slot自定義插槽方式展示。
<Popup bind:open={showConfirm} shadeClose="false" title="警告資訊" xclose zIndex="2001"
content="<div style='color:#00e0a1;padding:20px 40px;'>確認框(這裡是確認框提示資訊,這裡確認框提示資訊,這裡是確認框提示資訊)</div>"
btns={[
{text: '取消', click: () => showConfirm=false},
{text: '確定', style: 'color:#e63d23;', click: handleInfo},
]}
/>
<!-- 底部對話方塊 -->
<Popup bind:open={showFooter} anim="footer" type="footer" shadeClose="false" zIndex="1001"
content="確定刪除該條資料嗎?刪除後可在7天之內恢復資料,超過7天后資料就無法恢復啦!"
btns={[
{text: '恢復', style: 'color:#00e0a1;', click: handleInfo},
{text: '刪除', style: 'color:#ee0a24;', click: () => null},
{text: '取消', style: 'color:#a9a9a9;', click: () => showFooter=false},
]}
/>
<!-- ActionSheet底部彈出式選單 -->
<Popup bind:open={showActionSheet} anim="footer" type="actionsheet" zIndex="2020"
content="彈窗內容,告知當前狀態、資訊和解決方法,描述文字儘量控制在三行內"
btns={[
{text: '拍照', style: 'color:#09f;', disabled: true, click: handleInfo},
{text: '從手機相簿選擇', style: 'color:#00e0a1;', click: handleInfo},
{text: '儲存圖片', style: 'color:#e63d23;', click: () => null},
{text: '取消', click: () => showActionSheet=false},
]}
/>
<!-- ActionSheet底部彈出式選單(仿微信weui-picker頂部按鈕) -->
<Popup bind:open={showActionPicker} anim="footer" type="actionsheetPicker" round title="標題內容"
btns={[
{text: '取消', click: () => showActionPicker=false},
{text: '確定', style: 'color:#00e0a1;', click: () => null},
]}
>
<!-- 自定義內容 -->
<ul class="goods-list" style="padding:50px;text-align:center;">
<li>雙肩包</li>
<li>鞋子</li>
<li>運動褲</li>
</ul>
</Popup>
還支援函式式 多層混合 方式呼叫。
function handleInfo(e) {
console.log(e)
console.log('通過函式方式呼叫彈窗...')
let el = svPopup({
title: '標題',
content: `<div style="padding:20px;">
<p>函式式呼叫:<em style="color:#999;">svPopup({...})</em></p>
</div>`,
btns: [
{
text: '取消',
click: () => {
// 關閉彈窗
el.$set({open: false})
}
},
{
text: '確認',
style: 'color:#09f;',
click: () => {
svPopup({
type: 'toast',
icon: 'loading',
content: '載入中...',
opacity: .2,
time: 2
})
}
},
]
})
}
同樣也支援元件式和函式式混合呼叫。
svelte-popup引數配置
支援如下 20+ 引數混合呼叫。
<script>
// 是否開啟彈窗bind:open={showDialog}
export let open = false
// 彈窗識別符號
// export let id = 'svpopup-' + Math.random().toString(32)
export let id = undefined
// 標題
export let title = ''
// 內容
export let content = ''
// 彈窗型別
export let type = ''
// 自定義彈窗樣式
export let popupStyle = undefined
// toast圖示
export let icon = ''
// 是否顯示遮罩層
export let shade = true
// 點選遮罩層是否關閉
export let shadeClose = true
// 遮罩層透明度
export let opacity = ''
// 是否顯示圓角
export let round = false
// 是否顯示關閉圖示
export let xclose = false
// 關閉圖示位置
export let xposition = 'right'
// 關閉圖示顏色
export let xcolor = '#333'
// 彈窗動畫
export let anim = 'scaleIn'
// 彈窗位置
export let position = ''
// 長按/右鍵彈窗
export let follow = null
// 彈窗自動關閉時間
export let time = 0
// 彈窗層級
export let zIndex = 202203
// 彈窗按鈕組
export let btns = null
/* export let btns = [
{ text: '取消', style: 'color:#aaa', disabled: true, click: null },
{ text: '確定', style: 'color:#f90', click: null }
] */
// 函式式開啟|關閉回撥
export let onOpen = undefined
export let onClose = undefined
// 接收函式式移除指令
export let remove = undefined
// ...
</script>
彈窗模板及js處理部分。
<div class="sv__popup" class:opened class:sv__popup-closed={closeCls} id={id} style="z-index: {zIndex}" bind:this={el}>
{#if bool(shade)}<div class="vui__overlay" on:click={shadeClicked} style:opacity></div>{/if}
<div class="vui__wrap">
<div class="vui__wrap-section">
<div class="vui__wrap-child {type&&'popupui__'+type} anim-{anim} {position}" class:round style="{popupStyle}">
{#if title}<div class="vui__wrap-tit">{@html title}</div>{/if}
{#if icon&&type=='toast'}<div class="vui__toast-icon">{@html toastIcon[icon]}</div>{/if}
{#if $$slots.content}
<div class="vui__wrap-cnt"><slot name="content" /></div>
{:else}
{#if content}<div class="vui__wrap-cnt">{@html content}</div>{/if}
{/if}
<slot />
{#if btns}
<div class="vui__wrap-btns">
{#each btns as btn,index}
<span class="btn"style="{btn.style}" on:click={e => btnClicked(e, index)}>{@html btn.text}</span>
{/each}
</div>
{/if}
{#if xclose}<span class="vui__xclose {xposition}" style="color: {xcolor}" on:click={hide}></span>{/if}
</div>
</div>
</div>
</div>
/**
* @Desc svelte自定義移動端彈窗元件
* @Time andy by 2022/3/15
* @About Q:282310962 wx:xy190310
*/
<script>
// ...
import { onMount, afterUpdate, createEventDispatcher, tick } from 'svelte'
const dispatch = createEventDispatcher()
let opened = false
let closeCls = undefined
let toastIcon = {
loading: '',
success: '',
fail: '',
}
const bool = (boolean) => JSON.parse(boolean) ? true : false
onMount(() => {
console.log('監聽彈窗開啟...')
return () => {
console.log('監聽彈窗關閉...')
}
})
afterUpdate(() => {
// console.log('監聽彈窗更新...')
/* if(opened) {
if(!open) {
opened = false
dispatch('close')
}
}else if(open) {
opened = true
dispatch('open')
} */
})
$: if(open) {
show()
}else {
hide()
}
/**
* 開啟彈窗
*/
async function show() {
if(opened) return
opened = true
dispatch('open')
typeof onOpen == 'function' && onOpen()
zIndex = getZIndex() + 1
// 倒數計時關閉
if(time) {
index++
if(timer[index] != null) clearTimeout(timer[index])
timer[index] = setTimeout(() => {
hide()
}, parseInt(time)*1000)
}
// 長按|右鍵選單
if(follow) {
// ...
}
}
/**
* 關閉彈窗
*/
function hide() {
if(!opened) return
closeCls = true
setTimeout(() => {
opened = false
closeCls = false
open = false
// ...
}, 200)
}
// 點選遮罩層
function shadeClicked() {
if(bool(shadeClose)) {
hide()
}
}
// ...// 臨界座標點
function getPos(x, y, ow, oh, winW, winH) {
let l = (x + ow) > winW ? x - ow : x
let t = (y + oh) > winH ? y - oh : y
return [l, t]
}
</script>
一開始開發的時候只支援元件式呼叫。想著如果能支援函式式呼叫(插入元件至body,關閉即移除)就好了。
後來在svelte官網發現 new Component
可以傳入 props
引數,試了下,發現果然可以實現這種效果。const component = new Component(options)
如下是官網給的例子
https://svelte.dev/docs#run-t...
import App from './App.svelte';
const app = new App({
target: document.body,
props: {
// assuming App.svelte contains something like
// `export let answer`:
answer: 42
}
});
於是新建一個popup.js。
import Popup from './Popup.svelte'
let uuid = function() {
return 'svpopup-' + Math.floor(Math.random() * 10000)
}
export function svPopup(options = {}) {
options.id = uuid()
const mountNode = document.createElement('div')
document.body.appendChild(mountNode)
const app = new Popup({
target: mountNode,
props: {
...options,
open: true,
// 傳入函式移除指令
remove() {
document.body.removeChild(mountNode)
}
}
})
return app
}
export default Popup
通過如上方式就完美解決了匯出 元件式+函式式 的呼叫方式了。
通過一系列的學習,發現svelte還是挺不錯的,尤其編譯執行夠快,體積夠小。不過唯一有點遺憾的是還沒有找到類似vue全域性引入元件的方法。