vue3系列:vue3.0自定义全局弹层V3Layer|vue3.x pc桌面端弹窗组件
基于Vue3.0开发PC桌面端自定义对话框组件V3Layer。
前两天有分享一个vue3.0移动端弹出层组件,今天分享的是最新开发的vue3.0版pc端弹窗组件。
V3Layer 一款使用vue3.0开发的多功能PC网页端自定义弹窗组件。拥有超过10+种弹窗类型、30+种参数配置,支持拖拽(自定义拖拽区域)、缩放、最大化、全屏及自定义置顶层叠等功能。
v3layer的开发灵感同样来自之前v2版本,在功能效果上和之前的保持同步一致性。
◆ 引入组件
- // 在main.js中全局引入组件
- import { createApp } from 'vue'
- import App from './App.vue'
- // 引入Element-Plus组件库
- import ElementPlus from 'element-plus'
- import 'element-plus/lib/theme-chalk/index.css'
- // 引入弹窗组件v3layer
- import V3Layer from './components/v3layer'
- createApp(App).use(ElementPlus).use(V3Layer).mount('#app')
v3layer同样的支持组件式+函数式调用方式。
- 组件式写法
- <v3-layer
- v-model="showDialog"
- title="标题内容"
- content="<div style='color:#f57b16;padding:30px;'>这里是内容信息!</div>"
- z-index="2021"
- lockScroll="false"
- xclose
- resize
- dragOut
- :btns="[
- {text: '取消', click: () => showDialog=false},
- {text: '确认', style: 'color:#f90;', click: handleSure},
- ]"
- />
- <template v-slot:content>这里是自定义插槽内容信息!</template>
- </v3-layer>
- 函数式写法
v3layer支持30+种参数灵活配置,当弹窗类型为 message | notify | popover 调用如下:
v3layer.message({...}) v3layer.notify({...}) v3layer.popover({...})
- let $el = v3layer({
- title: '标题内容',
- content: '<div style='color:#f57b16;padding:30px;'>这里是内容信息!</div>',
- shadeClose: false,
- zIndex: 2021,
- lockScroll: false,
- xclose: true,
- resize: true,
- dragOut: true,
- btns: [
- {text: '取消', click: () => { $el.close() }},
- {text: '确认', click: () => handleSure},
- ]
- });
大家都知道vue2中可以通过 prototype 原型链来实现全局函数。那么vue3中如何实现全局方法呢?
其实vue3中也提供了两种挂载全局方法。 app.config.globalProperties 或 app.provide
- 通过 app.config.globalProperties.$v3layer = V3Layer 方式
- // vue2中调用
- methods: {
- showDialog() {
- this.$v3layer({...})
- }
- }
- // vue3中调用
- setup() {
- // 获取上下文
- const { ctx } = getCurrentInstance()
- ctx.$v3layer({...})
- }
- 通过 app.provide('v3layer', V3Layer) 方式
- // vue2中调用
- methods: {
- showDialog() {
- this.v3layer({...})
- }
- }
- // vue3中调用
- setup() {
- const v3layer = inject('v3layer')
- const showDialog = () => {
- v3layer({...})
- }
- return {
- v3layer,
- showDialog
- }
- }
大家可以根据喜好,选择一种方式即可。
◆ 预览图
◆ 编码实现
- v3layer默认配置参数
支持如下参数灵活使用,实现个性化弹窗效果。
- |props参数|
- v-model 是否显示弹框
- id 弹窗唯一标识
- title 标题
- content 内容(支持String、带标签内容、自定义插槽内容)***如果content内容比较复杂,推荐使用标签式写法
- type 弹框类型(toast|footer|actionsheet|actionsheetPicker|android|ios|contextmenu|drawer|iframe)
- layerStyle 自定义弹窗样式
- icon toast图标(loading | success | fail)
- shade 是否显示遮罩层
- shadeClose 是否点击遮罩时关闭弹窗
- lockScroll 是否弹窗出现时将body滚动锁定
- opacity 遮罩层透明度
- xclose 是否显示关闭图标
- xposition 关闭图标位置(left | right | top | bottom)
- xcolor 关闭图标颜色
- anim 弹窗动画(scaleIn | fadeIn | footer | fadeInUp | fadeInDown | fadeInLeft | fadeInRight)
- position 弹出位置(auto | ['100px','50px'] | t | r | b | l | lt | rt | lb | rb)
- drawer 抽屉弹窗(top | right | bottom | left)
- follow 跟随元素定位弹窗(支持元素.kk #kk 或 [e.clientX, e.clientY])
- time 弹窗自动关闭秒数(1、2、3)
- zIndex 弹窗层叠(默认8080)
- teleport 指定挂载节点(默认是挂载组件标签位置,可通过teleport自定义挂载位置) teleport="body | #xxx | .xxx"
- topmost 置顶当前窗口(默认false)
- area 弹窗宽高(默认auto)设置宽度area: '300px' 设置高度area:['', '200px'] 设置宽高area:['350px', '150px']
- maxWidth 弹窗最大宽度(只有当area:'auto'时,maxWidth的设定才有效)
- maximize 是否显示最大化按钮(默认false)
- fullscreen 全屏弹窗(默认false)
- fixed 弹窗是否固定
- drag 拖拽元素(可定义选择器drag:'.xxx' | 禁止拖拽drag:false)
- dragOut 是否允许拖拽到窗口外(默认false)
- lockAxis 限制拖拽方向可选: v 垂直、h 水平,默认不限制
- resize 是否允许拉伸尺寸(默认false)
- btns 弹窗按钮(参数:text|style|disabled|click)
- ++++++++++++++++++++++++++++++++++++++++++++++
- |emit事件触发|
- success 层弹出后回调(@success="xxx")
- end 层销毁后回调(@end="xxx")
- ++++++++++++++++++++++++++++++++++++++++++++++
- |event事件|
- onSuccess 层打开回调事件
- onEnd 层关闭回调事件
- v3layer模板组件及核心逻辑处理。
- <template>
- <div ref="elRef" v-show="opened" class="vui__layer" :class="{'vui__layer-closed': closeCls}" :id="id">
- <!-- //蒙版 -->
- <div v-if="JSON.parse(shade)" class="vlayer__overlay" @click="shadeClicked" :style="{opacity}"></div>
- <div class="vlayer__wrap" :class="['anim-'+anim, type&&'popui__'+type, tipArrow]" :style="[layerStyle]">
- <div v-if="title" class="vlayer__wrap-tit" v-html="title"></div>
- <div v-if="type=='toast'&&icon" class="vlayer__toast-icon" :class="['vlayer__toast-'+icon]" v-html="toastIcon[icon]"></div>
- <div class="vlayer__wrap-cntbox">
- <!-- 判断插槽是否存在 -->
- <template v-if="$slots.content">
- <div class="vlayer__wrap-cnt"><slot name="content" /></div>
- </template>
- <template v-else>
- <template v-if="content">
- <iframe v-if="type=='iframe'" scrolling="auto" allowtransparency="true" frameborder="0" :src="content"></iframe>
- <!-- message|notify|popover -->
- <div v-else-if="type=='message' || type=='notify' || type=='popover'" class="vlayer__wrap-cnt">
- <i v-if="icon" class="vlayer-msg__icon" :class="icon" v-html="messageIcon[icon]"></i>
- <div class="vlayer-msg__group"><div v-if="title" class="vlayer-msg__title" v-html="title"></div><div v-html="content"></div></div>
- </div>
- <div v-else class="vlayer__wrap-cnt" v-html="content"></div>
- </template>
- </template>
- <slot />
- </div>
- <div v-if="btns" class="vlayer__wrap-btns">
- <span v-for="(btn,index) in btns" :key="index" class="btn" :style="btn.style" @click="btnClicked($event,index)" v-html="btn.text"></span>
- </div>
- <span v-if="xclose" class="vlayer__xclose" :class="!maximize&&xposition" :style="{'color': xcolor}" @click="close"></span>
- <span v-if="maximize" class="vlayer__maximize" @click="maximizeClicked($event)"></span>
- <span v-if="resize" class="vlayer__resize"></span>
- </div>
- <!-- 优化拖拽卡顿 -->
- <div class="vlayer__dragfix"></div>
- </div>
- </template>
- /**
- * @Desc Vue3.0桌面端弹窗组件V3Layer
- * @Time andy by 2021-1
- * @About Q:282310962 wx:xy190310
- */
- <script>
- import { onMounted, onUnmounted, ref, reactive, watch, toRefs, nextTick } from 'vue'
- import domUtils from './utils/dom.js'
- // 索引,蒙层控制,定时器
- let $index = 0, $locknum = 0, $timer = {}, $closeTimer = null
- export default {
- props: {
- // ...
- },
- emits: [
- 'update:modelValue'
- ],
- setup(props, context) {
- const elRef = ref(null);
- const data = reactive({
- opened: false,
- closeCls: '',
- toastIcon: {
- // ...
- },
- messageIcon: {
- // ...
- },
- vlayerOpts: {},
- tipArrow: null,
- })
- onMounted(() => {
- if(props.modelValue) {
- open();
- }
- window.addEventListener('resize', autopos, false);
- })
- onUnmounted(() => {
- window.removeEventListener('resize', autopos, false);
- clearTimeout($closeTimer);
- })
- // 监听弹层v-model
- watch(() => props.modelValue, (val) => {
- // console.log('V3Layer is now [%s]', val ? 'show' : 'hide')
- if(val) {
- open();
- }else {
- close();
- }
- })
- // 打开弹窗
- const open = () => {
- if(data.opened) return;
- data.opened = true;
- typeof props.onSuccess === 'function' && props.onSuccess();
- const dom = elRef.value;
- // 弹层挂载位置
- if(props.teleport) {
- nextTick(() => {
- let teleportNode = document.querySelector(props.teleport);
- teleportNode.appendChild(dom);
- auto();
- })
- }
- callback();
- }
- // 关闭弹窗
- const close = () => {
- if(!data.opened) return;
- let dom = elRef.value;
- let vlayero = dom.querySelector('.vlayer__wrap');
- let ocnt = dom.querySelector('.vlayer__wrap-cntbox');
- let omax = dom.querySelector('.vlayer__maximize');
- data.closeCls = true;
- clearTimeout($closeTimer);
- $closeTimer = setTimeout(() => {
- data.opened = false;
- data.closeCls = false;
- if(data.vlayerOpts.lockScroll) {
- $locknum--;
- if(!$locknum) {
- document.body.style.paddingRight = '';
- document.body.classList.remove('vui__body-hidden');
- }
- }
- if(props.time) {
- $index--;
- }
- // 清除弹窗样式
- vlayero.style.width = vlayero.style.height = vlayero.style.top = vlayero.style.left = '';
- ocnt.style.height = '';
- omax && omax.classList.contains('maximized') && omax.classList.remove('maximized');
- data.vlayerOpts.isBodyOverflow && (document.body.style.overflow = '');
- context.emit('update:modelValue', false);
- typeof props.onEnd === 'function' && props.onEnd();
- }, 200)
- }
- // 弹窗位置
- const auto = () => {
- // ...
- autopos();
- // 全屏弹窗
- if(props.fullscreen) {
- full();
- }
- // 弹窗拖动|缩放
- move();
- }
- const autopos = () => {
- if(!data.opened) return;
- let oL, oT
- let pos = props.position;
- let isFixed = JSON.parse(props.fixed);
- let dom = elRef.value;
- let vlayero = dom.querySelector('.vlayer__wrap');
- if(!isFixed || props.follow) {
- vlayero.style.position = 'absolute';
- }
- let area = [domUtils.client('width'), domUtils.client('height'), vlayero.offsetWidth, vlayero.offsetHeight]
- oL = (area[0] - area[2]) / 2;
- oT = (area[1] - area[3]) / 2;
- if(props.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 : domUtils.scroll('left') + oL) + 'px';
- vlayero.style.top = parseFloat(isFixed ? oT : domUtils.scroll('top') + oT) + 'px';
- }
- }
- // 元素跟随定位
- const offset = () => {
- let oW, oH, pS
- let dom = elRef.value
- let vlayero = dom.querySelector('.vlayer__wrap');
- oW = vlayero.offsetWidth;
- oH = vlayero.offsetHeight;
- pS = domUtils.getFollowRect(props.follow, oW, oH);
- data.tipArrow = pS[2];
- vlayero.style.left = pS[0] + 'px';
- vlayero.style.top = pS[1] + 'px';
- }
- // 最大化弹窗
- const full = () => {
- // ...
- }
- // 恢复弹窗
- const restore = () => {
- let dom = elRef.value;
- let vlayero = dom.querySelector('.vlayer__wrap');
- let otit = dom.querySelector('.vlayer__wrap-tit');
- let ocnt = dom.querySelector('.vlayer__wrap-cntbox');
- let obtn = dom.querySelector('.vlayer__wrap-btns');
- let omax = dom.querySelector('.vlayer__maximize');
- let t = otit ? otit.offsetHeight : 0
- let b = obtn ? obtn.offsetHeight : 0
- if(!data.vlayerOpts.lockScroll) {
- data.vlayerOpts.isBodyOverflow = false;
- document.body.style.overflow = '';
- }
- props.maximize && omax.classList.remove('maximized')
- vlayero.style.left = parseFloat(data.vlayerOpts.rect[0]) + 'px';
- vlayero.style.top = parseFloat(data.vlayerOpts.rect[1]) + 'px';
- vlayero.style.width = parseFloat(data.vlayerOpts.rect[2]) + 'px';
- vlayero.style.height = parseFloat(data.vlayerOpts.rect[3]) + 'px';
- }
- // 拖动|缩放弹窗
- const move = () => {
- // ...
- }
- // 事件处理
- const callback = () => {
- // 倒计时关闭
- if(props.time) {
- $index++
- // 防止重复点击
- if($timer[$index] !== null) clearTimeout($timer[$index])
- $timer[$index] = setTimeout(() => {
- close();
- }, parseInt(props.time) * 1000)
- }
- }
- // 点击最大化按钮
- const maximizeClicked = (e) => {
- let o = e.target
- if(o.classList.contains('maximized')) {
- // 恢复
- restore();
- } else {
- // 最大化
- full();
- }
- }
- // 点击遮罩层
- const shadeClicked = () => {
- if(JSON.parse(props.shadeClose)) {
- close();
- }
- }
- // 按钮事件
- const btnClicked = (e, index) => {
- let btn = props.btns[index]
- if(!btn.disabled) {
- typeof btn.click === 'function' && btn.click(e)
- }
- }
- return {
- ...toRefs(data),
- elRef,
- close,
- maximizeClicked,
- shadeClicked,
- btnClicked,
- }
- }
- }
- </script>
大家可以看到在vue3中的写法和vue2中不一样了,很多都是在setup中处理,最后return出去,不过之前的vue2 optionsAPI写法仍然可用。
v3layer组件支持自定义拖拽区域 (drag:'#xxx'),是否拖动到窗口外 (dragOut:true)。还支持iframe弹窗类型 (type:'iframe')。
当配置 topmost:true 当前活动窗口会保持置顶状态。
Okey,基于Vue3开发自定义弹窗组件就分享到这里。希望对大家有所帮助哈!✍
最后附上一个Nuxt.js实例项目
Nuxt.js+Vue 手机端聊天:https://www.cnblogs.com/xiaoyan2017/p/13823195.html
vue3系列:vue3.0自定义全局弹层V3Layer|vue3.x pc桌面端弹窗组件的更多相关文章
- 基于Vue.js PC桌面端弹出框组件|vue自定义弹层组件|vue模态框
vue.js构建的轻量级PC网页端交互式弹层组件VLayer. 前段时间有分享过一个vue移动端弹窗组件,今天给大家分享一个最近开发的vue pc端弹出层组件. VLayer 一款集Alert.Dia ...
- React15.6.0实现Modal弹层组件
代码地址如下:http://www.demodashi.com/demo/12315.html 注:本文Demo环境使用的是我平时开发用的配置:这里是地址. 本文适合对象 了解React. 使用过we ...
- vue3系列:vue3.0自定义虚拟滚动条V3Scroll|vue3模拟滚动条组件
基于Vue3.0构建PC桌面端自定义美化滚动条组件V3Scroll. 前段时间有分享一个Vue3 PC网页端弹窗组件,今天带来最新开发的Vue3.0版虚拟滚动条组件. V3Scroll 使用vue3. ...
- svelte组件:svelte3自定义桌面PC端对话框组件svelte-layer
基于Svelte3.x开发pc网页版自定义弹窗组件svelteLayer. svelte-layer:基于svelte.js轻量级多功能pc桌面端对话框组件.支持多种弹窗类型.30+参数随意组合配置, ...
- 弹层组件-layer
layer是Layui的一个弹层组建,功能强大,总之我很喜欢,下面介绍这个组件的基本用法. 首先如果只需要使用layer而不想使用Layui可以单独下载layer组件包,页面引入jquery1.8以上 ...
- javascript右下角弹层及自动隐藏
在编写项目中总会需要有个右下角弹层提示公告的需求,怎么用更简单方面,更简洁代码,实现更好用户体验这个就是我们的所要做的内容.市场这块弹层很多,但功能不尽如人意.下面分享早些时候自己编写,以及现在还在应 ...
- Vue.js 桌面端自定义滚动条组件|vue美化滚动条VScroll
基于vue.js开发的小巧PC端自定义滚动条组件VScroll. 前段时间有给大家分享一个vue桌面端弹框组件,今天再分享最近开发的一个vue pc端自定义滚动条组件. vscroll 一款基于vue ...
- vue3系列:vue3.0自定义弹框组件V3Popup|vue3.x手机端弹框组件
基于Vue3.0开发的轻量级手机端弹框组件V3Popup. 之前有分享一个vue2.x移动端弹框组件,今天给大家带来的是Vue3实现自定义弹框组件. V3Popup 基于vue3.x实现的移动端弹出框 ...
- vue3.0自定义指令(drectives)
在大多数情况下,你都可以操作数据来修改视图,或者反之.但是还是避免不了偶尔要操作原生 DOM,这时候,你就能用到自定义指令. 举个例子,你想让页面的文本框自动聚焦,在没有学习自定义指令的时候,我们可能 ...
随机推荐
- Python音视频开发:消除抖音短视频Logo和去电视台标的实现详解
☞ ░ 前往老猿Python博文目录 ░ 一.引言 对于带Logo(如抖音Logo.电视台标)的视频,有三种方案进行Logo消除: 直接将对应区域用对应图像替换: 直接将对应区域模糊化: 通过变换将要 ...
- Python中使用百分号占位符的字符串格式化方法中%s和%r的输出内容有何不同?
Python中使用百分号占位符的字符串格式化方法中%s和%r表示需要显示的数据对应变量x会以str(x)还是repr(x)输出内容展示. 关于str和repr的关系请见: <Python中rep ...
- PyQt(Python+Qt)学习随笔:QAbstractItemView的editTriggers属性以及平台编辑键(platform edit key )
老猿Python博文目录 老猿Python博客地址 editTriggers属性 editTriggers属性用于确认哪些用户操作行为会触发ItemView中的数据项进入编辑模式. 此属性是由枚举类E ...
- django 自定义存储上传文件的文件名
一.需求: Django实现自定义文件名存储文件 使文件名看起来统一 避免收到中文文件导致传输.存储等问题 相同的文件也需要使用不同的文件名 二.实现思路: 思路: 生成14位随机字母加数字.后10位 ...
- [BJDCTF 2nd]xss之光
[BJDCTF 2nd]xss之光 进入网址之后发现存在.git泄露,将源码下载下来,只有index.php文件 <?php $a = $_GET['yds_is_so_beautiful']; ...
- gulp-sourcemaps的用法
1.项目文件夹中,安装gulp-sourcemaps插件 npm install --save gulp-sourcemaps 2.gulpfile.js文件,导入要用到的插件. 如: // 引入gu ...
- asp.net在线人数限制
1.网站启动初始化在线人数变量 Application["WebsiteCount"] = 0; 2.新的会话进来 只有在全新的会话进来的时候,该方法才会执行.可以过滤掉某些不需要 ...
- 自顶向下redis4.0(1)启动
redis4.0的启动流程 目录 redis4.0的启动流程 简介 正文 全局server对象 初始化配置 初始化服务器 事件主循环 参考文献 简介 redis 在接收客户端连接之前,大概做了以下几件 ...
- Tokyo 五年 IT 生活
今天阳光甚好,在家中小屋,闲来无事,回顾一下这五年的历程.我想从来东京的缘由.东京的环境.生活.IT这四个方面介绍一下. 首先,说一下为什么我会来到东京. 电子信息专业毕业,大学实验室学习IT,毕业后 ...
- ubuntu 设置apple主题
ubuntu 设置apple主题 参考地址,主要是看这个,很详细 https://linuxhint.com/gnome-tweak-tool-ubuntu-17-10/ 效果图 终端命令 $ sud ...