前两天有给大家分享一个 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 全局引入组件的办法。