基于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端弹窗组件就分享到这里。希望对大家有一些帮助~ ????