基于Svelte3.x开发pc网页版自定义弹窗组件svelteLayer。
svelte-layer:基于svelte.js轻量级多功能pc桌面端对话框组件。支持多种弹窗类型、30+参数随意组合配置,整合了拖拽/四周缩放/最大化/记忆弹窗位置/全屏/自定义层级等功能。

svelteLayer功能效果上有些类似layer.js插件。
◆ 快速引入
在需要使用组件功能的页面,引入组件。
import Layer, {svLayer} from '$lib/Layer'

svelteLayer支持标签式+函数式两种调用方式。
- 标签式调用
<!-- 询问框--><Layerbind: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>
弹窗模板及核心逻辑处理。
<divclass="vui__layer" class:opened class:vui__layer-closed={closeCls}id={id}bind:this={el}><!-- 遮罩层-->
{#if bool(shade)}<divclass="vlayer__overlay" on:click={shadeClicked}style:opacity></div>{/if}<!-- 主体--><divclass="vlayer__wrap {type&&'popui__'+type} anim-{anim}" style="{layerStyle}">
{#if title}<divclass="vlayer__wrap-tit">{@html title}</div>{/if}
{#if icon&&type=='toast'}<divclass="vlayer__toast-icon vlayer__toast-{icon}">{@html toastIcon[icon]}</div>{/if}<divclass="vlayer__wrap-cntbox"><!-- 判断content插槽是否存在-->
{#if $$slots.content}<divclass="vlayer__wrap-cnt"><slotname="content"/></div>
{:else}
{#if content}<!-- iframe-->
{#if type=='iframe'}<divclass="vlayer__wrap-iframe"><iframescrolling="auto" allowtransparency="true" frameborder="0" src={content}></iframe></div><!-- message|notify|popover-->
{:else if type=='message' || type=='notify' || type=='popover'}<divclass="vlayer__wrap-cnt">
{#if icon}<iclass="vlayer-msg__icon {icon}">{@html messageIcon[icon]}</i>{/if}<divclass="vlayer-msg__group">
{#if title}<divclass="vlayer-msg__title">{@html title}</div>{/if}<divclass="vlayer-msg__content">{@html content}</div></div></div><!-- 加载动态组件-->
{:else if type == 'component'}<svelte:componentthis={content}/>
{:else}<divclass="vlayer__wrap-cnt">{@html content}</div>
{/if}
{/if}
{/if}<slot/></div><!-- 按钮组-->
{#if btns}<divclass="vlayer__wrap-btns">
{#each btns as btn,index}<spanclass="btn" class:btn-disabled={btn.disabled}style="{btn.style}">{@html btn.text}</span>
{/each}</div>
{/if}
{#if xclose}<spanclass="vlayer__xclose" style="color: {xcolor}" on:click={hide}></span>
{/if}
{#if maximize}<spanclass="vlayer__maximize" on:click={maximizeClicked}></span>{/if}<!-- 缩放-->
{#if resize}<spanclass="vlayer__groupresize"><iclass="vlayer__resize LT"></i><iclass="vlayer__resize RT"></i><iclass="vlayer__resize LB"></i><iclass="vlayer__resize RB"></i></span>
{/if}</div><!-- 优化拖拽卡顿--><divclass="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()
}/**
* 开启弹窗*/
asyncfunction 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'
}// 最大化弹窗 asyncfunction full() {// ... }// 复位弹窗 asyncfunction restore() {// ... }// 拖拽缩放function move() {
let isfixed= bool(fixed)
let isdragOut= bool(dragOut)
let c= {}
let vlayero= el.querySelector('.vlayer__wrap')
let otit= el.querySelector('.vlayer__wrap-tit')
let ocnt= el.querySelector('.vlayer__wrap-cntbox')
let obtn= el.querySelector('.vlayer__wrap-btns')
let odrag= el.querySelector(drag)
let oresize= el.querySelectorAll('.vlayer__resize')
let ofix= el.querySelector('.vlayer__dragfix')// 拖拽if(odrag) {
odrag.style.cursor= util.isIE()?'move' :'grab'
util.on(odrag,'mousedown',function(e) {// ... })
}
util.on(document,'mousemove',function(e) {if(c.dragTrigger) {
let iL= e.clientX- c.pos[0]+ c.area[0]
let iT= e.clientY- c.pos[1]+ c.area[1]
let fixL= isfixed?0 : c.scroll[1]
let fixT= isfixed?0 : c.scroll[2]
let iMaxL= c.client[0]+ fixL- c.area[2]
let iMaxT= c.client[1]+ fixT- c.area[3]
let oMaxT= c.scroll[0]- c.area[3]// 边界检测if(isdragOut) {
iT= iT<0?0 : iT
iT= (iT> oMaxT)? oMaxT : iT
}else {
iL= (iL< fixL)? fixL : iL
iL= (iL> iMaxL)? iMaxL : iL
iT= (iT< fixT)? fixT : iT
iT= (iT> iMaxT)? iMaxT : iT
}// 记录拖拽弹窗坐标 c.dragPosition= {
width: c.area[2],
height: c.area[3],
x: iL,
y: iT
}// 限制拖拽方向 dragDir=='horizontal'? (vlayero.style.left= iL+'px')
:
dragDir=='vertical'? (vlayero.style.top= iT+'px')
:
(vlayero.style.left= iL+'px', vlayero.style.top= iT+'px')
}// 边角缩放if(c.resizeTrigger&& c.elem) {// ... }
})
util.on(document,'mouseup',function() {
c.dragTrigger&& (delete c.dragTrigger, ofix.style.display='none',typeof dragEnd==='function'&& dragEnd(dragPosition)
)
c.resizeTrigger&& (delete c.resizeTrigger, ofix.style.display='none'
)
document.onmouseup=null
})
}// 点击最大化按钮function maximizeClicked(e) {
let o= e.targetif(o.classList.contains('maximized')) {
restore()
}else {
full()
}
}//...</script>
svelte-layer还支持类型为message | notify | popover 弹窗。

svLayer.message({})
svLayer.notify({})
svLayer.popover({})
调用方式如上,只支持函数式调用。
svelte-layer支持自定义拖拽区域drag: ‘#header’ ,是否拖拽到窗口外dragOut:true 。还支持iframe弹窗类型type: ‘iframe’ ,配置topmost:true 即可让当前活动窗口保持置顶状态。
该组件还有一大亮点,就是支持动态引入外部组件。
import Counter from '$lib/Counter.svelte'
// 动态加载组件(函数式调用)
function showComponentLayer() {
svLayer({
type: 'component',
title: '动态加载组件',
content: Counter,
resize: true,
xclose: true,
maximize: true,
area: ['360px', '250px']
})
}<!-- 组件调用--><Layerbind:open={showComponent}content="这里是内容信息" resize drag=".vlayer__wrap-cnt"
btns={[{text: '确认', style: 'color:#f60;', click: ()=> showComponent=false},
]}
on:open={handleOpen} on:close={handleClose}
><svelte:fragmentslot="content"><Counter/></svelte:fragment></Layer>
OK,基于Svelte.js开发pc端弹窗组件就分享到这里。希望对大家有一些帮助~ ????
正文完