前幾天有給大家分享一個 svelte+sass實戰微信app介面聊天例項
今天要給大家分享的是全新研發的svelte.js自定義桌面版對話方塊元件SvelteLayer。
如下圖:在lib目錄下新建一個Layer元件目錄。
引入svelte-layer元件
彈窗元件支援元件式(Layer
)+函式式(svLayer
)兩種呼叫方式。
import Layer, {svLayer} from '$lib/Layer'
- 函式式寫法
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
})
}
},
]
})
}
- 元件式寫法
<!-- 詢問框 -->
<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},
]}
/>
兩種寫法可以單獨使用,如果是一些複雜的彈窗場景,可考慮兩種寫法混合呼叫。
svelte-layer配置引數
支援如下超過30+引數隨意搭配呼叫。
<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>
彈窗模板及js邏輯處理模組。
<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'
}
// 最大化彈窗
async function full() {
// ...
}
// 復位彈窗
async function 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.target
if(o.classList.contains('maximized')) {
restore()
}else {
full()
}
}
//...
</script>
另外svelte-layer還支援動態引入外部元件。
import Counter from '$lib/Counter.svelte'
// 動態載入元件(函式式呼叫)
function showComponentLayer() {
svLayer({
type: 'component',
title: '動態載入元件',
content: Counter,
resize: true,
xclose: true,
maximize: true,
area: ['360px', '250px']
})
}
<!-- 元件呼叫 -->
<Layer bind:open={showComponent} content="這裡是內容資訊" resize drag=".vlayer__wrap-cnt"
btns={[
{text: '確認', style: 'color:#f60;', click: () => showComponent=false},
]}
on:open={handleOpen} on:close={handleClose}
>
<svelte:fragment slot="content">
<Counter />
</svelte:fragment>
</Layer>
另外還封裝了類似element-ui種message/notify/popover彈窗型別。
/**
* Message提示訊息
* @param {object} options
* @param {string} icon圖示(info/success/error/warning/loading)
*/
svLayer.message = function(options) {
typeof options == 'string' && (options = {content: options})
return svLayer({
icon: 'info',
anim: 'fadeInUp',
position: 't',
lockScroll: false,
shade: false,
time: 2,
layerStyle: 'margin-top: 10px',
...options,
title: null,
type: 'message'
})
}
/**
* Notification通知訊息
* @param {object} options
* @param {string} icon圖示(info/success/error/warning/loading)
*/
svLayer.notify = function(options) {
typeof options == 'string' && (options = {content: options})
return svLayer({
anim: 'fadeInRight',
position: 'rt',
lockScroll: false,
shade: false,
xclose: true,
time: 5,
layerStyle: 'margin: 20px 0 0 -20px',
...options,
type: 'notify'
})
}
/**
* Popover氣泡框
* @param {object} options
* @param {string} icon圖示(info/success/error/warning/loading)
*/
svLayer.popover = function(options) {
return svLayer({
anim: 'fadeIn',
lockScroll: false,
shade: false,
xclose: true,
...options,
type: 'popover'
})
}
好了,以上就是svelte.js開發自定義pc端彈窗外掛的一些分享。後續還會分享一些svelte自定義元件及實戰案例專案。