vc-align源码分析 -- ant-design-vue系列
vc-align源码分析
源码地址:https://github.com/vueComponent/ant-design-vue/tree/main/components/vc-align
1 基础代码
1.1 名词约定
需要对齐的节点叫source,对齐的目标叫target。
1.2 props
提供了两个参数:
align:对齐的配置target:一个函数,用于获取对齐的目标dom
1.3 主要逻辑
- 增加了一个
dom,用来挂载source节点,同时拿到它的引用。 - 提供了一个方法
align,在组件初始化/定位方式改变/对齐目标改变的时候,重新执行对齐方法。
代码如下:
import { defineComponent, ref, onMounted, watch, PropType } from 'vue';
import { alignElement } from 'dom-align';
import { AlignType, TargetType } from './interface';export default defineComponent({name: 'Align',props: {align: {type: Object as PropType<AlignType>,required: true},target: {type: [Object, Function] as PropType<TargetType>,required: true}},setup(props, { slots }) {const nodeRef = ref<HTMLElement | null>(null);/*** 用来对齐的方法*/const align = () => {if (!nodeRef.value) return;const { align: latestAlign, target: latestTarget } = props;let result: any;let targetElement: HTMLElement | null = null;if (typeof latestTarget === 'function') {targetElement = latestTarget();}if (targetElement && targetElement.nodeType === Node.ELEMENT_NODE) {/*** 调用对齐的库方法*/result = alignElement(nodeRef.value, targetElement, latestAlign);}};onMounted(() => {align();});/*** 监控对齐方式和target的改变,重新执行对齐*/watch(() => [props.align, props.target],() => {align();},{ immediate: true, deep: true, flush: 'post' });return () => {const child = slots.default?.();if (child) {return <div ref={nodeRef}>{child}</div>;}return null;};}
});
1.4 补充:dom-align 库
官方地址:https://yiminghe.me/dom-align/
1.4.1 基础用法
import domAlign from 'dom-align';// use domAlign
// sourceNode's initial style should be position:absolute;left:-9999px;top:-9999px;const alignConfig = {points: ['tl', 'tr'], offset: [10, 20], targetOffset: ['30%','40%'], overflow: { adjustX: true, adjustY: true },
};domAlign(sourceNode, targetNode, alignConfig);
1.4.2 alignConfig对象的详细配置
| Name | Type | Description |
|---|---|---|
| points | String[2] | source元素和targer元素的对齐方式,比如 [‘tr’, ‘cc’],意思是source元素的右上角和target元素的中心对齐。点的取值可以是t, b, c, l, r。 |
| offset | Number[2] | source元素的偏移量,offset[0] 是x轴,offset[1]是y轴。如果数组中包含了百分比,这个也是相对应source区域来说的。 |
| targetOffset | Number[2] | 和上面一致,只不过都是针对target元素来说的。 |
| overflow | Object: { adjustX: boolean, adjustY: boolean, alwaysByViewport:boolean } | 如果adjustX是true,那么如果source元素在x轴方向不可见,会自动调整位置。比如指定source元素在target右边,但是右边区域不足以放得下source,则会自动修改到做左边展示。adjustY同理。如果alwaysByViewport是true,那么当source不在视口中时,会自动调整。 |
| useCssRight | Boolean | 是否使用css的right属性代替left属性去定位。 |
| useCssBottom | Boolean | 是否使用css的bottom属性代替top属性去定位。 |
| useCssTransform | Boolean | 是否使用css的transform属性代替 left/top/right/bottom来定位。 |
2 源码解析
2.1 可以优化的点
- 我们给
source增加了一个div,用来获取引用,这个dom节点是不必要,可以去掉。 - 只监控了 对齐方式/
target引用 的变化,没有监控source和target大小的变化,需要在这些属性变化时,重新对齐。 - 需要监控窗口大小的变化,重新对齐。
2.2 实现
2.2.1 监控window变化
这个有resize事件,直接组册即可。
组件需要接受一个props,表示是否需要监控window变化。
export const alignProps = {monitorWindowResize: Boolean,
};
代码如下,flush: post是为了保证页面已经渲染结束,可以拿到dom引用。
/**
* 用来记录监控事件的id
*/
const winResizeRef = ref<{ remove: Function }>(null);watch(() => props.monitorWindowResize,(monitorWindowResize) => {if (monitorWindowResize) {/*** 需要监控window大小变化,但是以前没有注册过监控事件*/if (!winResizeRef.value) {winResizeRef.value = window.addEventListener('resize', forceAlign);}} else if (winResizeRef.value) {/*** 如果不需要监控,但是已经监控过了,那就取消监控*/winResizeRef.value.remove();winResizeRef.value = null;}},{ immediate: true, flush: 'post' }
);
2.2.2 监控source和target的变化
- 需要手写一个监控的函数
这里需要一个新的接口:ResizeObserver https://developer.mozilla.org/zh-CN/docs/Web/API/ResizeObserver
使用这个接口,可以监听一个DOM节点的变化,这种变化包括但不仅限于:
- 某个节点的出现和隐藏
- 某个节点的大小变化
我们用它来观察指定的元素,如果元素变化,执行指定的回调。
export function monitorResize(element: HTMLElement, callback: Function) {/*** 1 初始化一个观察器* onResize 是元素变化后的回调*/const resizeObserver = new ResizeObserver(onResize);/*** 2 观察指定的DOM元素 element*/if (element) {resizeObserver.observe(element);}// ....../*** 3 返回一个函数,用于取消观察*/return () => {resizeObserver.disconnect();};
}
每次都用当前大小和上次的大小比较,如果不一致,执行callback回调。
export function monitorResize(element: HTMLElement, callback: Function) {// ......let prevWidth: number = null;let prevHeight: number = null;/*** 4 当元素大小变化时,调用用户传入的 callback 方法*/function onResize([{ target }]: ResizeObserverEntry[]) {if (!document.documentElement.contains(target)) return;const { width, height } = target.getBoundingClientRect();const fixedWidth = Math.floor(width);const fixedHeight = Math.floor(height);if (prevWidth !== fixedWidth || prevHeight !== fixedHeight) {// https://webkit.org/blog/9997/resizeobserver-in-webkit/Promise.resolve().then(() => {callback({ width: fixedWidth, height: fixedHeight });});}prevWidth = fixedWidth;prevHeight = fixedHeight;}
}
- 在页面挂载的时候,注册监控事件;在页面属性更新的时候(比如source或者target变化时),需要清除旧的事件,注册新的事件
onMounted(() => {nextTick(() => {/*** goAlign 用来维护监控事件,同时执行对齐方法* 实现在下面。*/goAlign();});
});onUpdated(() => {nextTick(() => {goAlign();});});
因为要清除旧的事件,所以需要需要保存 注册方法返回的 resizeObserver.disconnect(),方便执行清除的时候调用;同时记录下来当前引用的dom节点,来判断是否需要注册新的监听事件。
interface MonitorRef {element?: HTMLElement; // 当前`dom`节点的引用cancel: () => void; // 监控事件的取消方法
}// Listen for target updated
const targetResizeMonitor = ref<MonitorRef>({cancel: () => {},
});
// Listen for source updated
const sourceResizeMonitor = ref<MonitorRef>({cancel: () => {},
});
goAlign()的实现
const goAlign = () => {const target = props.target;const element = getElement(target);const point = getPoint(target);/*** onMounted 的时候,必定执行;onUpdated 的时候,只有source的引用变了才会执行* 清除旧的监听事件,注册新的*/ if (nodeRef.value !== sourceResizeMonitor.value.element) {sourceResizeMonitor.value.cancel();sourceResizeMonitor.value.element = nodeRef.value;sourceResizeMonitor.value.cancel = monitorResize(nodeRef.value, forceAlign);}/*** 如果缓存的target和当前的target不一致,或者对齐方式不一致,就执行对齐方法* 同时如果target变了,清除旧的监听事件,注册新的*/if (cacheRef.value.element !== element ||!isSamePoint(cacheRef.value.point, point) ||!isEqual(cacheRef.value.align, props.align)) {forceAlign();// Add resize observerif (resizeMonitor.value.element !== element) {resizeMonitor.value.cancel();resizeMonitor.value.element = element;resizeMonitor.value.cancel = monitorResize(element, forceAlign);}}
};
2.2.3 重写对齐的方法
因为我们监控了元素大小的变化,触发频率很高,也就是说对齐方法执行的频率也会非常高。
所以需要一个方法,这个方法需要实现类似防抖的功能。源码是使用useBuffer实现的,我们先看一下这个方法。
export const alignProps = {monitorBufferTime: Number,
};/**
* 返回了一个强制执行的方法和一个取消执行的方法
*/
const [forceAlign, cancelForceAlign] = useBuffer(() => {// ...... 对齐的方法},computed(() => props.monitorBufferTime),
);
- useBuffer的实现
/*** 这个函数设计用于控制一个基于时间缓冲的触发逻辑,确保在一定时间间隔内(由buffer参数指定)* 即使多次尝试触发,也只有一次实际执行callback的机会,除非通过强制执行(force参数为true)来绕过这个缓冲逻辑。** 提供了执行的方法和取消执行的方法*/
export default (callback: () => boolean, buffer: ComputedRef<number>) => {let called = false;let timeout = null;function cancelTrigger() {clearTimeout(timeout);}function trigger(force?: boolean) {// ......}return [trigger,() => {called = false;cancelTrigger();},];
};
执行方法trigger的实现如下:
- 不在回调过程中:直接设置定时
- 如果是强制触发:取消旧的定时,设置新的定时
- 在回调过程中:取消旧的定时,设置新的定时
function trigger(force?: boolean) {// 如果不在回调过程中 || 强制触发,则if (!called || force === true) {// 执行一遍callback,如果返回了false,就不需要延迟if (callback() === false) {// Not delay since callback cancelled selfreturn;}called = true;// 取消上次的定时,重新定时cancelTrigger();timeout = setTimeout(() => {called = false;}, buffer.value);} else {// 在回调过程中:取消上次的定时,重新定时cancelTrigger();timeout = setTimeout(() => {called = false;trigger();}, buffer.value);}
}
当buffer时间结束后,会执行对齐函数。
- 对齐的方法
const cacheRef = ref<{ element?: HTMLElement; point?: TargetPoint; align?: AlignType }>({});
const nodeRef = ref();
const [forceAlign, cancelForceAlign] = useBuffer(() => {const {disabled: latestDisabled,target: latestTarget,align: latestAlign,onAlign: latestOnAlign,} = props;if (!latestDisabled && latestTarget && nodeRef.value) {const source = nodeRef.value;/*** 获取了目标元素或者对齐点。*/let result: AlignResult;const element = getElement(latestTarget);const point = getPoint(latestTarget);/*** 缓存目标元素的信息和对齐方式*/cacheRef.value.element = element;cacheRef.value.point = point;cacheRef.value.align = latestAlign;// 🚁 IE浏览器在元素对齐后会失去焦点,所以需要在对齐后重新聚焦/*** 记录了当前文档中的活动元素(activeElement),以便在对齐操作后恢复焦点*/const { activeElement } = document;// 只有元素可见才需要对齐if (element && isVisible(element)) {result = alignElement(source, element, latestAlign);} else if (point) {result = alignPoint(source, point, latestAlign);}restoreFocus(activeElement, source);/*** 如果调用者需要在对齐后做一些事情,就执行props传进来的回调方法*/if (latestOnAlign && result) {latestOnAlign(source, result);}return true;}return false;},computed(() => props.monitorBufferTime),
);
target节点为啥要缓存下来?
在onUpdated中,调用了goAlign()。 props中的target是一个函数,可能对于同一个target节点,引用发生变化(调用者每次都给target一个新的函数),引起不必要的重新对齐操作。
2.2.4 给插槽元素增加ref引用
这里的实现比较简单,先看代码。主要逻辑就是cloneElement,在复制的时候重写了他的属性。
return () => {const child = slots?.default();if (child) {return cloneElement(child[0], { ref: nodeRef }, true, true);}return null;
};
看一下这个函数的实现。调用了vue的cloneVNode方法,把{ ref: nodeRef }加入到虚拟节点的属性中。
import { cloneVNode } from 'vue';export function cloneElement<T, U>(vnode: VNode<T, U> | VNode<T, U>[],nodeProps: Record<string, any> &Omit<VNodeProps, 'ref'> & { ref?: VNodeProps['ref'] | RefObject } = {},override = true,mergeRef = false,
): VNode<T, U> {let ele = vnode;if (Array.isArray(vnode)) {ele = filterEmpty(vnode)[0];}if (!ele) {return null;}const node = cloneVNode(ele as VNode<T, U>, nodeProps as any, mergeRef);// cloneVNode内部是合并属性,这里改成覆盖属性node.props = (override ? { ...node.props, ...nodeProps } : node.props) as any;return node;
}
3 效果演示
3.1 resize变化
当窗口大小变化时,对自适应对齐方式。以纵向为例。

3.2 source 和target大小变化
分别修改二者大小,都可以重新触发对齐操作。

3.3 插槽引用
source节点没有增加一个div包裹,同时也拿到了它的引用进行定位。

相关文章:
vc-align源码分析 -- ant-design-vue系列
vc-align源码分析 源码地址:https://github.com/vueComponent/ant-design-vue/tree/main/components/vc-align 1 基础代码 1.1 名词约定 需要对齐的节点叫source,对齐的目标叫target。 1.2 props 提供了两个参数: align:对…...
计算机网络(四) —— 简单Tcp网络程序
目录 一,服务器初始化 1.0 部分文件代码 1.1 关于Tcp协议 1.2 创建和绑定套接字 1.3 监听 二,服务器启动 2.1 获取连接 2.2 提供服务 2.3 客户端启动源文件 Main.cc 二,客户端编写 2.1 关于Tcp客户端 2.2 客户端代码 2.3 效果…...
简单的Linux Ftp服务搭建
简单的Linux FTP服务搭建 1.需求 公司有一个esb文件传输代理,其中我们程序有文件传输功能,需要将本地文件传输到esb文件代理服务器上,传输成功之后发送http请求,告知esb将固定文件进行传输到对应外围其他服务的文件目录中&#…...
SQL的高级查询练习知识点(day24)
目录 1 学习目标 2 基础查询 2.1 语法 2.2 例子 3 条件查询 3.1 含义 3.2 语法 3.3 条件表达式 3.3.1 条件运算符 3.3.2 例子 3.4 逻辑表达式 3.4.1 逻辑运算符 3.4.2 例子 3.5 模糊查询 3.5.1 概述 3.5.2 例子 4 DISTINCT关键字 4.1 含义 4.2 例子 5 总结…...
Python条件表达式优化的10个实例
Python 中的条件表达式(也称为三元运算符)是一种简洁的语法,用于在单个表达式中执行 if-else 逻辑。虽然它们本身并不直接“优化”代码的执行速度,但它们可以使代码更加简洁、易读,并且有助于避免不必要的嵌套或复杂的…...
oatpp apiclient 客户端get,post请求python fastapi demo
最新用fastapi搞了个服务端,python功能太强了,就是环境不好弄,弄好后,不要轻易换python版本,不要装多个python版本 前面搞了个oatpp webapi服务端,现在要用客户端,为什么用opatpp客户端,因为他不再带其他库了 demo: 我的请求比较简单,就是向python 的 fastapi服务端…...
RK3568平台(内存篇)EMMC介绍
一.eMMC是什么 eMMC (Embedded Multi Media Card)是MMC协会订立、主要针对手机或平板电脑等产品的内嵌式存储器标准规格。由一个嵌入式存储解决方案组成,带有MMC(多媒体卡)接口、快闪存储器设备及主控制器。所有都在一个小型的BGA 封装。接口速度高达每秒52MBytes,eMMC具…...
Python批量读取身份证信息录入系统和重命名
前言 大家好, 如果你对自动化处理身份证图片感兴趣,可以尝试以下操作:从身份证图片中快速提取信息,填入表格并提交到网页系统。如果你无法完成这个任务,我们将在“Python自动化办公2.0”课程中详细讲解实现整个过程。…...
IBM Storwize V7000存储控制器故障节点报错574
背景:由于客户机房搬迁,需要下电迁移设备。该存储自2016年投入生产使用后,从未关过机,已正常运行七八年时间,期间只更换过硬盘,无其他硬件故障。 在GUI界面点击关闭系统后,大概等了40分钟&…...
通信工程学习:什么是SSB单边带调制、VSB残留边带调制、DSB抑制载波双边带调制
SSB单边带调制、VSB残留边带调制、DSB抑制载波双边带调制 SSB单边带调制、VSB残留边带调制、DSB抑制载波双边带调制是三种不同的调制方式,它们在通信系统中各有其独特的应用和特点。以下是对这三种调制方式的详细解释: 一、SSB单边带调制 1、SSB单边带…...
MapSet之二叉搜索树
系列文章: 1. 先导片--Map&Set之二叉搜索树 2. Map&Set之相关概念 目录 前言 1.二叉搜索树 1.1 定义 1.2 操作-查找 1.3 操作-新增 1.4 操作-删除(难点) 1.5 总体实现代码 1.6 性能分析 前言 TreeMap 和 TreeSet 是 Java 中基于搜索树实现的 M…...
OpenCV图像分割教程
OpenCV 图像分割教程 OpenCV 是一个非常强大的计算机视觉库,支持各种图像处理任务。图像分割是 OpenCV 支持的一个重要功能,它用于将图像划分为不同的区域,识别感兴趣的部分。我们将通过介绍 OpenCV 中的图像分割方法,包括基础功…...
python科学计算:NumPy 线性代数与矩阵操作
1 NumPy 中的矩阵与数组 在 NumPy 中,矩阵实际上是一种特殊的二维数组,因此几乎所有数组的操作都可以应用到矩阵上。不过,矩阵运算与一般的数组运算存在一定的区别,尤其是在点积、乘法等操作中。 1.1 创建矩阵 矩阵可以通过 Nu…...
Unity面向对象补全计划 之 List<T>与class(非基础)
C# & Unity 面向对象补全计划 泛型-CSDN博客 关于List,其本质就是C#封装好的一个数组,是一个很好用的轮子,所以并不需要什么特别说明 问题描述 假设我们有一个表示学生的类 Student,每个学生有姓名和年龄两个属性。我们需要创…...
ant design vue+vue3+ts+xlsx实现表格导出问excel文件(带自定义表头)~
1、首先默认你已安装ant design vue、xlsx 库、及file-saver。 2、导入: import * as XLSX from xlsx; import { saveAs } from file-saver; 注:这里的xlsx导入不能这么写,否则会报错,原因是版本不一致,语法向上兼容…...
基于Python爬虫的淘宝服装数据分析项目
文章目录 一.项目介绍二.爬虫代码代码分析 三. 数据处理四. 数据可视化 一.项目介绍 该项目是基于Python爬虫的淘宝服装数据分析项目,以致于帮助商家了解当前服装市场的需求,制定更加精确的营销策略。首先,需要爬取淘宝中关于服装的大量数据…...
Tomcat控制台乱码问题已解决(2024/9/7
步骤很详细,直接上教程 问题复现: 情景一 情景二 原因简述 这是由于编码不一致引起的,Tomcat启动后默认编码UTF-8,而Windows的默认编码是GBK。因此你想让其不乱码,只需配置conf\logging.properties的编码格式即可 解决…...
vue通过html2canvas+jspdf生成PDF问题全解(水印,分页,截断,多页,黑屏,空白,附源码)
前端导出PDF的方法不多,常见的就是利用canvas画布渲染,再结合jspdf导出PDF文件,代码也不复杂,网上的代码基本都可以拿来即用。 如果不是特别追求完美的情况下,或者导出PDF内容单页的话,那么基本上也就满足业…...
服务器数据恢复—Raid磁盘阵列故障类型和常见故障原因
出于尽可能避免数据灾难的设计初衷,RAID解决了3个问题:容量问题、IO性能问题、存储安全(冗余)问题。从数据恢复的角度讨论RAID的存储安全问题。 常见的起到存储安全作用的RAID方案有RAID1、RAID5及其变形。基本设计思路是相似的:当部分数据异…...
C++字符串中的string类操作
愿我如星君如月,夜夜流光相皎洁。 ——《车逍遥篇》【宋】范成大 目录 正文: 主要特点: 基本操作: 代码演示: 总结: 今天我们接着上次的章节继续,这次我们来说一个为解决上个方法的缺陷而诞…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...
