基於Svelte3.x開發pc網頁版自定義彈窗組件svelteLayer。 svelte-layer:基於svelte.js輕量級多功能pc桌面端對話框組件。支持多種彈窗類型、30+參數隨意組合配置,整合了拖拽/四周縮放/最大化/記憶彈窗位置/全屏/自定義層級等功能。 svelteLayer功能效果 ...
基於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]) / 2 if(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' }