前几天有给大家分享一个 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自定义组件及实战案例我的项目。