当前位置: 首页 > news >正文

PopupInner源码分析 -- ant-design-vue系列

PopupInner源码分析 – ant-design-vue系列

1 综述

上一篇讲解了vc-align的工作原理,也就是对齐是如何完成的。这一篇主要讲述包裹 Align的组件:PopupInner组件是如何工作的。

PopupInner主要是对动画状态的管理,比如打开弹窗的时候,弹出的动画是从上到下的,关闭的时候正好相反。我们把动画时长改成两秒,进行观察。

在这里插入图片描述

**动画的管理,更深层次来说是对元素类名的管理。**接下来需要搞清楚在动画的过程中,状态是如何发生变化的。

2 极简代码

使用vue3提供的动画组件Transition来包裹Align组件,但是没有对Transition组件设置任何属性,所以暂时还没有动画效果。

return () => (<Transition>{props.visible ? (<Align align={props.align} target={props.target}>{slots.default?.()}</Align>) : null}</Transition>
);

3 源码实现

3.1 前置知识

组件Transition:https://cn.vuejs.org/guide/built-ins/transition.html#transition-on-appear

3.2 PopupInner组件定义的动画状态

在这里插入图片描述

源码地址:https://github.com/vueComponent/ant-design-vue/blob/main/components/vc-trigger/Popup/useVisibleStatus.ts

在源码中有这样一个hook,监控visible的变化,并且提供了当前状态和修改状态的函数。doMeasure 是另一个hook提供的函数,目的是得到source节点的宽/高度。

 // ======================== Status ========================const [status, goNextStatus] = useVisibleStatus(visible, doMeasure);

useVisibleStatus函数中,注释如下:

 /*** 每个组件正确工作的步骤如下,弹出窗口应遵循这个流程。* measure - 检查拉伸尺寸值* align - 让组件对齐位置* aligned - 再次重新对齐,以防止其他className更改了大小(这一步后续再解释)* afterAlign - 选择下一步是触发运动还是结束* beforeMotion - 应将motion重置为invisible,以便CSSMotion可以进行正常运动* motion - 执行动作* stable 一切结束*/

对源码进行拆解:

  • 整体框架:
type PopupStatus = null | 'measure' | 'align' | 'aligned' | 'motion' | 'stable';
type Func = () => void;
const StatusQueue: PopupStatus[] = ['measure', 'align', null, 'motion'];export default (visible: Ref<boolean>,doMeasure: Func,
): [Ref<PopupStatus>, (callback?: () => void) => void] => {// 弹出状态const status = ref<PopupStatus>(null);// raf = (callback: FrameRequestCallback) => window.requestAnimationFrame(callback);// rafRef 就是requestAnimationFrame执行器的引用,这个hook不做拆解了。const rafRef = ref<number>();// 标记组件的销毁状态const destroyRef = ref(false);const goNextStatus = () => {// ......}onMounted(() => {// ......})return [status, goNextStatus];
}

🎯 状态驱动如图所示:

在这里插入图片描述

  • 监控visible的变化

visible变化的时候,重新从measure状态开始流转。

watch(visible,() => {setStatus('measure');},{ immediate: true, flush: 'post' },
);
  • 监听status的变化

在组件挂载时,监控了status的变化。

如果当前是measure,调用传入的doMeasure,测量target的大小。接下来会设置新状态:

  1. 如果status === 'measure',则执行setStatus('align')
  2. 如果status === 'align',这里因为要多次定位,所以nextStatus为空,也就是无法自动进入下个状态
  3. 如果status === 'motion'nextStatus同样为空,无法自动进入下个状态

所以,当popup处于alignmotion状态时,都需要等待动作完成,由外部调用goNextStatus来进入下个状态。

onMounted(() => {// Go next statuswatch(status,() => {switch (status.value) {case 'measure':doMeasure();break;default:}if (status.value) {rafRef.value = raf(async () => {const index = StatusQueue.indexOf(status.value);const nextStatus = StatusQueue[index + 1];if (nextStatus && index !== -1) {setStatus(nextStatus);}});}},{ immediate: true, flush: 'post' },);
});function setStatus(nextStatus: PopupStatus) {if (!destroyRef.value) {status.value = nextStatus;}
}
  • goNextStatus 函数

可以看到,正好弥补了上一个watch不能自动触发的状态变更。

function goNextStatus(callback?: () => void) {// 取消原先的动作,注册新的。目的是为了动画的流程。cancelRaf();rafRef.value = raf(() => {// Only align should be manually triggerlet newStatus = status.value;switch (status.value) {case 'align':newStatus = 'motion';break;case 'motion':newStatus = 'stable';break;default:}setStatus(newStatus);callback?.();});
}
  • 其他部分

都是一些清理函数。

onBeforeUnmount(() => {destroyRef.value = true;cancelRaf();
});function cancelRaf() {raf.cancel(rafRef.value);
}

3.3 核心变量

先看一下渲染函数,最重要的就是transitionPropsmergedStyle,其他变量暂时不管。

return (<Transition// ......{...transitionProps}v-slots={{default: () => {return !destroyPopupOnHide || props.visible ? (<Align// ......v-slots={{default: () => (<div// ......style={mergedStyle}>{childNode}</div>),}}></Align>) : null;},}}></Transition>
);

对代码进行debug,观察这两个变量在动画状态过程中的变化。可以看到,在第一次measure过程结束后:

const transitionProps = {name: 'ant-slide-up',appear: true,enterFromClass: 'ant-slide-up-enter ant-slide-up-enter-prepare',enterActiveClass: 'ant-slide-up-enter ant-slide-up-enter-prepare',enterToClass: 'ant-slide-up-enter ant-slide-up-enter-active',leaveFromClass: ' ant-slide-up-leave',leaveActiveClass: 'ant-slide-up-leave ant-slide-up-leave-active',leaveToClass: 'ant-slide-up-leave ant-slide-up-leave-active'
};const mergedStyle = [{ minWidth: '76px', opacity: 0, pointerEvents: 'none' }, null];// 后续opacity会变成null,弹窗逐步显现。从源码中可以清晰的看到opacity: statusValue === 'motion' || statusValue === 'stable' || !visible.value ? null : 0,pointerEvents: !visible.value && statusValue !== 'stable' ? 'none' : null,

这个就是动画的核心了,其他代码都是为这个服务的。比如对齐popup的位置;控制内部status状态,进而让动画在对齐完成后再开始。

动画的源码:https://github.com/vueComponent/ant-design-vue/blob/main/components/style/core/motion/slide.less

3.4 代码详解

由于代码逻辑都穿插在一起,所以先把所有逻辑分开看一下,最后在用一个流程进行串联。

遵照源码对代码的分块。

3.4.1 Measure
// ======================= Measure ========================
/**
* 如果 stretch 是width,那么stretchStyle返回元素的 width;如果stretch 是minWidth,那么stretchStyle返回元素的 minWidth
* height也是一样的。
*
* 开始时 width = height = 0px
* 只有调用measureStretchStyle才能获得目标元素的属性
*/
const [stretchStyle, measureStretchStyle] = useStretchStyle(toRef(props, 'stretch'));const doMeasure = () => {if (props.stretch) {measureStretchStyle(props.getRootDomNode());}
};const visible = ref(false);
let timeoutId: any;
watch(() => props.visible,val => {clearTimeout(timeoutId);if (val) {/*** 如果visible变成true,那么延迟变更*/timeoutId = setTimeout(() => {visible.value = props.visible;});} else {visible.value = false;}},{ immediate: true },
);
3.4.2 Aligns
 // ======================== Aligns ========================
/**
* 解释见下文
*/
const prepareResolveRef = ref<(value?: unknown) => void>();/**
*	Align组件的target可以接受函数或者对象,获取的时候也要区分。
*/
const getAlignTarget = () => {if (props.point) {return props.point;}return props.getRootDomNode;
};/**
*	调用Align组件的对齐方法
*/
const forceAlign = () => {alignRef.value?.forceAlign();
};/**
* popupDomNode是弹窗节点,matchAlign是对齐的结果reuslt
* 使用align-dom对齐,会有自适应视口的位置调整,因此result并不一定是是设置的位置
*/
const onInternalAlign = (popupDomNode: HTMLElement, matchAlign: AlignType) => {/*** 根据对齐结果获取类名,比如默认的左上角对齐,类名是 ant-dropdown-placement-bottomLeft*/const nextAlignedClassName = props.getClassNameFromAlign(matchAlign);const preAlignedClassName = alignedClassName.value;if (alignedClassName.value !== nextAlignedClassName) {alignedClassName.value = nextAlignedClassName;}/*** 如果当前状态是align,并且新的类名和旧的不同(也就是对齐方式不同),就重新执行对齐方法,拿到新的对齐结果再比较* 直到对齐无误后,status进入下个阶段motion,同时放开动画,进入动画的下个阶段*/if (status.value === 'align') {// Repeat until not more align neededif (preAlignedClassName !== nextAlignedClassName) {Promise.resolve().then(() => {forceAlign();});} else {goNextStatus(() => {prepareResolveRef.value?.();});}props.onAlign?.(popupDomNode, matchAlign);}
};
  • prepareResolveRef

是一个Promiseresolve方法,执行这个方法,可以让Promise结束。这个promise的定义在Motion模块。

/**
* 从名字可以看出,这个是动画开始前的一个阶段。
*/
const onShowPrepare = () => {return new Promise(resolve => {prepareResolveRef.value = resolve;});
};

使用的地方在Transition 组件,也就是说,onBeforeEnter要想结束,需要等到prepareResolveRef.value?.()的执行。

<Transition// ......onBeforeEnter={onShowPrepare}
</Transition>
  • onInternalAlign

Align组件中,有以下这句代码。执行的就是onInternalAlign这个函数,传入sourceresult

if (latestOnAlign && result) {latestOnAlign(source, result);
}
3.4.3 Motion
// ======================== Motion ========================
const motion = computed(() => {/*** 如果设置了动画,就使用设置好的,否则使用默认的。也就是3.3 展示的变量*/const m = typeof props.animation === 'object' ? props.animation : getMotion(props as any);['onAfterEnter', 'onAfterLeave'].forEach(eventName => {/*** 拦截了两个状态* 如果动画状态是onAfterEnter或者onAfterLeave,也就是打开和关闭的结束时间点。* 就手动把status状态设置为stable,同时执行原动画逻辑。*/const originFn = m[eventName];m[eventName] = node => {goNextStatus();// 结束后,强制 stablestatus.value = 'stable';originFn?.(node);};});return m;
});const onShowPrepare = () => {return new Promise(resolve => {prepareResolveRef.value = resolve;});
};/**
* 如果不需要动画,且status是动画状态,则直接进入下个阶段stable
*/
watch([motion, status],() => {if (!motion.value && status.value === 'motion') {goNextStatus();}},{ immediate: true },
);expose({forceAlign,getElement: () => {return (elementRef.value as any).$el || elementRef.value;},
});const alignDisabled = computed(() => {if ((props.align as any)?.points && (status.value === 'align' || status.value === 'stable')) {return false;}return true;
});

3.5 主流程讲解

在这里插入图片描述

  1. Align组件使用了v-show属性,可以应用Transition的动画效果。由visible变量控制。
  2. visible变成true时,触发了useVisibleStatus这个hookstatus状态开始流转。
  3. useVisibleStatus这个hook中,status被设置成了measure
  4. 还是在这个hook中,因为statuswatch监听,因此触发了回调,执行了doMeasure方法,算出popup的样式,同时status被推到align
  5. 此时再次触发useVisibleStatus这个hookstatus的监听,状态被推到了null,也就是这个hook暂时不能控制状态了。
  6. 因为doMeasure方法的执行,source的大小发生了变化,对齐方法forceAlign开始执行(详情请看上一篇)。
  7. 每次forceAlign执行的末尾,都会执行latestOnAlign(source, result);对应的就是PopupInner中的onInternalAlign方法。
  8. 这个方法中,会多次调用forceAlign(),直到source被准确定位(排除各种视口变化对位置的影响),以便动画的位置没有错误。
  9. 当位置稳定后,onInternalAlign会手动将状态推进到下一步motion。同时执行prepareResolveRef.value?.(),让css动画也进入下一个阶段。
  10. 如果动画执行完成,通过劫持动画的onAfterEnter阶段,推进状态到stable,到此一次动画结束。

4 总结

本篇介绍了PopupInner的源码实现,由于状态变化的复杂,所以只要理解流程即可,在实际开发中,我们的对齐定位大多不会如此复杂。如果只允许超着一个方向对齐,那么只要为Transtion设置类名即可。

相关文章:

PopupInner源码分析 -- ant-design-vue系列

PopupInner源码分析 – ant-design-vue系列 1 综述 上一篇讲解了vc-align的工作原理&#xff0c;也就是对齐是如何完成的。这一篇主要讲述包裹 Align的组件&#xff1a;PopupInner组件是如何工作的。 PopupInner主要是对动画状态的管理&#xff0c;比如打开弹窗的时候&#…...

Maven 的 pom.xml 文件中<dependency> 元素及其各个参数的解释

在 Maven 的 pom.xml 文件中&#xff0c;<dependency> 标签用于定义项目依赖的外部库。每个 <dependency> 元素包含了一系列的子元素&#xff0c;这些子元素定义了依赖库的各种属性。下面是一个典型的 <dependency> 元素及其各个参数的解释&#xff1a; <…...

【信创】Linux终端禁用USB存储 _ 统信 _ 麒麟 _ 方德

原文链接&#xff1a;【信创】Linux终端禁用USB存储 | 统信 | 麒麟 | 方德 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在Linux终端下禁用USB存储设备的文章。禁用USB存储设备可以提高系统的安全性&#xff0c;防止未经授权的人员将数据拷贝到外部存储设备或…...

开放API接口时要注意的安全处理总结

开发API接口&#xff1a;开放给别人调用的接口。未经过安全处理的开发API接口安全弱点&#xff1a;数据窃取&#xff08;密码等信息被窃取&#xff0c;盗刷&#xff0c;敏感信息的等&#xff09;——RSA/DES加密&#xff1a; 签名机制在API接口中的应用&#xff1a;签名用于验证…...

FastGPT自定义插件的icon

最近研究FastGPT的自定义插件&#xff0c;经过好几天的折磨&#xff0c;终于实现了一个简单的发送邮件功能&#xff0c;但是呢在使用的时候发现插件的icon是默认的fastgpt的logo&#xff0c;那肯定得自定义一个啊。直接说方法&#xff1a; 1、自定义插件下面的template.json文件…...

SprinBoot+Vue旅游网站的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质…...

代码随想录刷题day27丨455.分发饼干 ,376. 摆动序列 ,53. 最大子序和

代码随想录刷题day27丨455.分发饼干 ,376. 摆动序列 ,53. 最大子序和 1.贪心算法理论基础 贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 这么说有点抽象&#xff0c;来举一个例子&#xff1a; 例如&#xff0c;有一堆钞票&#xff0c;你可以拿走十张&a…...

Detect It Easy

Detect It Easy&#xff08;简称 DIE&#xff09;项目的网址为 https://github.com/horsicq/Detect-It-Easy 下载完安装包后&#xff0c;直接双击die.exe即可进入到操作界面 工具介绍&#xff1a; 它可以用来检测程序架构和文件类型。如图所示。其中&#xff0c;「模式」说明程…...

c++开关灯

题目描述 现有 &#x1d45b;n 盏灯排成一排&#xff0c;从左到右依次编号为&#xff1a;11&#xff0c;22&#xff0c;……&#xff0c;&#x1d45b;n。然后依次执行 &#x1d45a;m 项操作。 操作分为两种&#xff1a; 指定一个区间 [&#x1d44e;,&#x1d44f;][a,b]&…...

DevOps实现CI/CD实战(六)- Jenkins集成k8s

十、 Jenkins集成k8s Jenkins在集成K8s之前&#xff0c;需要搭建k8s集群&#xff0c;具体搭建步骤&#xff0c;完整笔记 https://github.com/ITenderL/ITenderL.github.io/tree/main/docs/DevOps&#xff0c; 包括完整的DevOps的笔记。 1. 准备部署的yml文件 pipeline.yml …...

张雪峰:物联网行业迎高光时刻!如何选择?我们诚聘销售工程师!

作为一间10多年的物联网公司&#xff0c;各位求职人士可以看看我们其中一个招聘要求&#xff0c;和自己需求结合分析分析&#xff0c;希望对你们有所帮助。 【公司实力底蕴】 盈电智控物联网科技&#xff08;广东&#xff09;有限公司&#xff0c;2024年7月成立&#xff0c;是…...

利用多文件编程实现顺序表的创建,判满,插入,输出

文章目录 &#x1f34a;自我介绍&#x1f34a;利用多文件编程实现顺序表的创建&#xff0c;判满&#xff0c;插入&#xff0c;输出seqlist.cseqlist.hmain.c 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以&#xff1a;点赞关注评论收藏&#xff08;一键四连&#xff…...

百度快照劫持之JS劫持诊断与恢复一例

劫持现象&#xff1a; 百度搜索结果中&#xff0c;被劫持网站出现在搜索结果中&#xff0c; 点击进入网站&#xff0c;网站显示正常&#xff0c;数秒后网站自动跳转到彩票网站f51688.com/ff6/。但是第二次点击搜索结果&#xff0c;正常进入网站缺不会跳转到彩票网站。 初步认…...

深入探讨Go语言中的切片与数组操作

在编程世界中&#xff0c;数组一直是非常流行的数据结构&#xff0c;主要有两个原因&#xff1a;其一是简单易懂&#xff0c;其二是非常灵活&#xff0c;可以存储多种不同类型的数据。在Go语言中&#xff0c;数组的用法有其独特的特点&#xff0c;但与此同时&#xff0c;Go语言…...

【WPS Excel】复制表格时,提示“图片太大,超过部份将被截去“ 问题

WPS表格 2019版本 升级到 WPS最新版 WPS-支持多人在线协作编辑Word、Excel和PPT文档_WPS官方网站 使用最新版就能够解决这个问题&#xff0c;如果仍旧无法解决可以勾选如下配置 重启Excel解决。 请勾选&#xff1a;文件 - 选项 - 编辑 - 不提示且不压缩文件中的图像...

驱动(RK3588S)第九课时:多节点驱动与函数接口

目录 一、多节点概念1、所用到的结构体说明2、函数接口主要是read和write函数2.1、把应用层的数据拷贝给底层2.2、把应用层的数据拷贝给底层 3、应用层的read和write函数4、底层的read和write函数二、ioctl控制命令接口1、概念2、函数介绍应用层和驱动层 三、代码与现象1.编写L…...

Linux系统下配置MySQL

1. 寻找MySQL的配置文件 MySQL的配置文件通常位于以下位置&#xff1a; 在大多数Linux系统上&#xff0c;主配置文件通常位于/etc/mysql/my.cnf或/etc/my.cnf。在macOS上&#xff0c;如果你使用Homebrew安装MySQL&#xff0c;配置文件通常位于/usr/local/etc/my.cnf。在Window…...

信捷 XD PLC POU编程之FB

在使用信捷的POU方式编程&#xff0c;可以建立两种POU:FB和FC。 FB和FC这两种POU又各自可以建立梯形图语言POU和C语言POU。 函数块&#xff08;FB&#xff09;是把反复使用的部分程序块转换成一种通用部件&#xff0c;他可以在程序中反复被调用&#xff0c;不仅 提高了程序的开…...

终于有人把云计算、大数据和人工智能讲明白了!

引言 在当今数字化时代&#xff0c;云计算、大数据和人工智能成为了全球科技界的热门话题。这些技术的迅猛发展以及应用范围的不断扩大&#xff0c;正深刻地改变着我们的生活和工作方式。云计算为我们提供了有效的计算和存储能力&#xff0c;大数据则以海量的信息资源为基础&a…...

【编程底层思考】详解Java内存模型(JMM)原理及其作用

Java内存模型&#xff08;Java Memory Model, JMM&#xff09;是Java虚拟机&#xff08;JVM&#xff09;的一个核心概念&#xff0c;它定义了Java程序中各种变量&#xff08;线程共享变量&#xff09;的访问规则&#xff0c;以及在并发环境下&#xff0c;为了确保数据的可见性、…...

Goofys安全最佳实践:保护你的S3文件系统访问的终极指南

Goofys安全最佳实践&#xff1a;保护你的S3文件系统访问的终极指南 【免费下载链接】goofys a high-performance, POSIX-ish Amazon S3 file system written in Go 项目地址: https://gitcode.com/gh_mirrors/go/goofys 在当今云原生时代&#xff0c;安全访问云存储变得…...

美国人形机器人发展浅析

美国人形机器人产业正从实验室研发向工业实用化与商业化加速过渡&#xff0c;主要企业&#xff08;波士顿动力、特斯拉、Figure AI等&#xff09;均已推出量产级产品&#xff0c;覆盖工业制造、军事应用等核心场景&#xff0c;技术迭代与规模化部署成为当前行业关键词。一、主要…...

如何用FanControl彻底告别电脑噪音?Windows风扇控制终极解决方案

如何用FanControl彻底告别电脑噪音&#xff1f;Windows风扇控制终极解决方案 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_T…...

PID调参翻车实录:STM32驱动编码电机时,P值过大为何电机啸叫还振荡?

PID调参实战&#xff1a;STM32编码电机啸叫与振荡问题深度解析 当你在深夜实验室里第一次听到电机发出刺耳的啸叫声&#xff0c;同时观察到示波器上速度曲线像过山车一样上下震荡时&#xff0c;那种既困惑又兴奋的感觉&#xff0c;相信每个做过电机控制的工程师都深有体会。这不…...

RMBG-1.4开源模型解析:AI净界如何实现SOTA级Alpha通道生成

RMBG-1.4开源模型解析&#xff1a;AI净界如何实现SOTA级Alpha通道生成 你有没有遇到过这样的烦恼&#xff1f;想给产品换个背景&#xff0c;结果抠出来的图边缘全是锯齿&#xff1b;想给自己做一张透明背景的证件照&#xff0c;头发丝却和背景糊在一起&#xff1b;或者想用AI生…...

「码动四季·开源同行」go实战案例:如何在 Go 微服务中实现负载均衡?

在上文章中&#xff0c;我们已经介绍了负载均衡的相关概念以及在服务高可用架构中的重要性&#xff0c;也了解了几种主流负载均衡算法的实现。在本文中&#xff0c;我们将在Go微服务实例中具体使用负载均衡技术&#xff0c;并详细说明如何基于服务发现来实现负载均衡的微服务间…...

Pixel Aurora Engine镜像部署:多用户并发生成的Streamlit服务配置

Pixel Aurora Engine镜像部署&#xff1a;多用户并发生成的Streamlit服务配置 1. 像素极光引擎简介 Pixel Aurora&#xff08;像素极光&#xff09;是一款基于AI扩散模型的高端绘图工作站&#xff0c;采用独特的复古像素游戏风格界面设计。这款工具能够将文字描述转化为极具视…...

3步解锁Windows运行安卓应用:APK-Installer轻量解决方案

3步解锁Windows运行安卓应用&#xff1a;APK-Installer轻量解决方案 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 在数字化办公与娱乐融合的今天&#xff0c;安卓应用…...

C++数组和指针的声明与使用指南

数组声明语法 在 C 中声明数组的语法为&#xff1a; 数据类型 数组名[数组大小]; 示例&#xff1a; int myArray[10]; // 声明一个包含 10 个整数的数组 数组初始化 声明时可直接初始化&#xff1a; int myArray[5] {10, 20, 30, 40, 50}; 部分初始化时&#xff0c;未指定值的…...

从静态到动态:开源AI视频生成工具如何用3分钟改变你的创作方式

从静态到动态&#xff1a;开源AI视频生成工具如何用3分钟改变你的创作方式 【免费下载链接】stepvideo-ti2v 项目地址: https://ai.gitcode.com/StepFun/stepvideo-ti2v 在AI技术日新月异的今天&#xff0c;视频创作正经历着一场前所未有的革命。阶跃星辰推出的Step-Vi…...