svelte组件:svelte3自定义桌面PC端对话框组件svelte-layer
基于 Svelte3.x 开发 pc网页版 自定义弹窗组件 svelteLayer 。
svelte-layer :基于 svelte.js 轻量级多功能 pc桌面端 对话框组件。支持多种弹窗类型、 30+ 参数随意组合配置,整合了 拖拽/四周缩放/最大化/记忆弹窗位置/全屏/自定义层级 等功能。
svelteLayer功能效果上有些类似layer.js插件。
◆ 快速引入
在需要使用组件功能的页面,引入组件。
import Layer, {svLayer} from '$lib/Layer'
svelteLayer支持 标签式+函数式 两种调用方式。
- 标签式调用
<!-- 询问框 --> < Layer bind:open ={showConfirm} shadeClose ="false" title ="警告信息" xclose zIndex ="2001" lockScroll ={false} resize dragOut
content ="<div style='color:#00e0a1;padding:20px 40px;'>这里是确认框提示信息</div>" btns ={[ {text: '取消', click: () = > showConfirm=false},
{text: '确定', style: 'color:#e63d23;', click: handleInfo},
]}
/>
- 函数式调用
function handleInfo(e) {
let el = svLayer({
title: '标题' ,
content: ` <div style="padding:20px;"> <p>函数式调用:<em style="color:#999;">svLayer({...})</em></p> </div>`, resize: true ,
btns: [
{
text: '取消' ,
click: () => { // 关闭弹窗 el.$set({open: false })
}
},
{
text: '确认' ,
style: 'color:#09f;' ,
click: () => {
svLayer({
type: 'toast' ,
icon: 'loading' ,
content: '加载中...' ,
opacity: . 2 ,
time: 2 })
}
},
]
})
}
支持标签式和函数式混合搭配调用,还支持如上图 动态加载 外部组件。
◆ 参数配置
svelte-layer默认支持如下参数自定义配置。
<script context="module"> let index = 0 // 用于控制倒计时临时索引 let lockNum = 0 // 用于控制锁定屏幕临时索引 </script> <script> // 是否打开弹窗bind:open={showDialog} export let open = false // 弹窗标识 export let id = undefined // 标题 export let title = '' // 内容 export let content = '' // 弹窗类型 export let type = '' // 自定义样式 export let layerStyle = undefined // 自定义类名 export let customClass = '' // toast图标 export let icon = '' // 是否显示遮罩层 export let shade = true // 点击遮罩层关闭 export let shadeClose = true // 锁定屏幕 export let lockScroll = true // 遮罩层透明度 export let opacity = '' // 是否显示关闭图标 export let xclose = false // 关闭图标位置 export let xposition = 'right' // 关闭图标颜色 export let xcolor = '#000' // 弹窗动画 export let anim = 'scaleIn' // 弹出位置(auto | ['100px','50px'] | t | r | b | l | lt | rt | lb | rb) export let position = 'auto' // 抽屉弹窗 export let drawer = '' // 右键弹窗定位 export let follow = null // 弹窗自动关闭时间 export let time = 0 // 弹窗层级 export let zIndex = 202204 // 置顶弹窗 export let topmost = false // 弹窗大小 export let area = 'auto' // 弹窗最大宽度 export let maxWidth = 375 // 弹窗是否最大化 export let maximize = false // 弹窗是否全屏 export let fullscreen = false // 是否固定 export let fixed = true // 是否拖拽 export let drag = '.vlayer__wrap-tit' // 是否拖拽屏幕外 export let dragOut = false // 限制拖拽方向 vertical|horizontal export let dragDir = '' // 拖拽结束回调 {width: 120, height: 120, x: 100, y: 100} export let dragEnd = undefined // 是否缩放 export let resize = false // 弹窗按钮事件 export let btns = null /* export let btns = [
{text: '取消', style: 'color:red', disabled: true, click: null},
{text: '确定', style: 'color:blue', click: null}
] */ // 函数式打开|关闭回调 export let onOpen = undefined
export let onClose = undefined
export let beforeClose = undefined // 接收函数移除指令 export let remove = undefined
import { onMount, afterUpdate, createEventDispatcher, tick } from 'svelte' const dispatch = createEventDispatcher() // ... </script>
弹窗模板及核心逻辑处理。
< div class ="vui__layer" class:opened class:vui__layer-closed ={closeCls} id ={id} bind:this ={el} >
<!-- 遮罩层 --> {#if bool(shade)} < div class ="vlayer__overlay" on:click ={shadeClicked} style:opacity ></ div > {/if} <!-- 主体 -->
< div class ="vlayer__wrap {type&&'popui__'+type} anim-{anim}" style ="{layerStyle}" > {#if title} < div class ="vlayer__wrap-tit" > {@html title} </ div > {/if}
{#if icon& &type =='toast'} < div class ="vlayer__toast-icon vlayer__toast-{icon}" > {@html toastIcon[icon]} </ div > {/if} < div class ="vlayer__wrap-cntbox" >
<!-- 判断content插槽是否存在 --> {#if $$slots.content} < div class ="vlayer__wrap-cnt" >< slot name ="content" /></ div > {:else}
{#if content} <!-- iframe --> {#if type=='iframe'} < div class ="vlayer__wrap-iframe" >
< iframe scrolling ="auto" allowtransparency ="true" frameborder ="0" src ={content} ></ iframe >
</ div >
<!-- message|notify|popover --> {:else if type=='message' || type=='notify' || type=='popover'} < div class ="vlayer__wrap-cnt" > {#if icon} < i class ="vlayer-msg__icon {icon}" > {@html messageIcon[icon]} </ i > {/if} < div class ="vlayer-msg__group" > {#if title} < div class ="vlayer-msg__title" > {@html title} </ div > {/if} < div class ="vlayer-msg__content" > {@html content} </ div >
</ div >
</ div >
<!-- 加载动态组件 --> {:else if type == 'component'} < svelte:component this ={content} /> {:else} < div class ="vlayer__wrap-cnt" > {@html content} </ div > {/if}
{/if}
{/if} < slot />
</ div ><!-- 按钮组 --> {#if btns} < div class ="vlayer__wrap-btns" > {#each btns as btn,index} < span class ="btn" class:btn-disabled ={btn.disabled} style ="{btn.style}" > {@html btn.text} </ span > {/each} </ div > {/if}
{#if xclose} < span class ="vlayer__xclose" style ="color: {xcolor}" on:click ={hide} ></ span > {/if}
{#if maximize} < span class ="vlayer__maximize" on:click ={maximizeClicked} ></ span > {/if} <!-- 缩放 --> {#if resize} < span class ="vlayer__groupresize" >
< i class ="vlayer__resize LT" ></ i >
< i class ="vlayer__resize RT" ></ i >
< i class ="vlayer__resize LB" ></ i >
< i class ="vlayer__resize RB" ></ i >
</ span > {/if} </ div >
<!-- 优化拖拽卡顿 -->
< div class ="vlayer__dragfix" ></ div >
</ div >< script >
/* *
* @Desc Svelte.js桌面端对话框组件SvelteLayer
* @Time andy by 2022-04
* @About Q:282310962 wx:xy190310 */// ...
onMount(() => {
console.log( ' 监听弹窗开启 ' )
window.addEventListener( ' resize ' , autopos, false ) return () => {
console.log( ' 监听弹窗关闭 ' )
window.removeEventListener( ' resize ' , autopos, false )
}
})
afterUpdate(() => {
console.log( ' 监听弹窗更新 ' )
}) // 动态监听开启/关闭
$: if (open) {
show()
} else {
hide()
} /* *
* 开启弹窗 */ async function show() { if (opened) return opened = true dispatch( ' open ' ) typeof onOpen === ' function ' && onOpen() // 避免获取弹窗宽高不准确
await tick()
zIndex = util.getZIndex(zIndex) + 1 auto()
} /* *
* 关闭弹窗 */
function hide() { // ...
} // 弹窗位置
function auto() {
autopos() // 全屏弹窗
if (fullscreen) {
full()
} // 拖拽|缩放
move()
} // 弹窗定位
function autopos() { if ( ! opened) return let ol, ot
let pos = position
let isfixed = bool(fixed)
let vlayero = el.querySelector( ' .vlayer__wrap ' ) if ( ! isfixed || follow) {
vlayero.style.position = ' absolute ' }
let area = [util.client( ' width ' ), util.client( ' height ' ), vlayero.offsetWidth, vlayero.offsetHeight]
ol = (area[ 0 ] - area[ 2 ]) / 2 ot = (area[ 1 ] - area[ 3 ]) / 2if (follow) {
offset()
} else { typeof pos === ' object ' ? (
ol = parseFloat(pos[ 0 ]) || 0 , ot = parseFloat(pos[ 1 ]) || 0 ) : (
pos == ' t ' ? ot = 0 :
pos == ' r ' ? ol = area[ 0 ] - area[ 2 ] :
pos == ' b ' ? ot = area[ 1 ] - area[ 3 ] :
pos == ' l ' ? ol = 0 :
pos == ' lt ' ? (ol = 0 , ot = 0 ) :
pos == ' rt ' ? (ol = area[ 0 ] - area[ 2 ], ot = 0 ) :
pos == ' lb ' ? (ol = 0 , ot = area[ 1 ] - area[ 3 ]) :
pos == ' rb ' ? (ol = area[ 0 ] - area[ 2 ], ot = area[ 1 ] - area[ 3 ]) : null )
vlayero.style.left = parseFloat(isfixed ? ol : util.scroll( ' left ' ) + ol) + ' px ' vlayero.style.top = parseFloat(isfixed ? ot : util.scroll( ' top ' ) + ot) + ' px ' }
} // 跟随定位
function offset() {
let ow, oh, ps
let vlayero = el.querySelector( ' .vlayer__wrap ' )
ow = vlayero.offsetWidth
oh = vlayero.offsetHeight
ps = util.getFollowRect(follow, ow, oh)
tipArrow = ps[ 2 ]
vlayero.style.left = ps[ 0 ] + ' px ' vlayero.style.top = ps[ 1 ] + ' px ' }