Vue3 实现双盒子定位Overlay的示例


Posted in Vue.js onDecember 22, 2020

在 Vue 3 中,使用 <Teleport> 可以很优雅的把某个组件渲染到根节点之外的节点,同时使其渲染的内容不丧失响应式和对应的生命周期函数调用。那么基于此,用 <Teleport> 实现相对于某一元素的 Overlay 。 实际上,这篇文章跟 Vue3 的关系不大,只是通过 Vue3 讲解一类 Overlay 的设计方法。

原理

要实现相对于某一元素的 Overlay 需要依靠两个元素,Origin 和 Panel,Origin 表示相对于的元素,而 Panel 表示 Overlay 本身,实现方法主要有两种。

文本流定位法,基于 position 的 absolute 和 relative 特性,将 Panel 形成相对于 Origin 的位置来定位的方式。
Overlay 基于 Origin 做固定偏移的双盒子定位法,也就是本文需要讲解的方法。
实现 首先,通过<Teleport>,能够建立顶层 Overlay,也就是在根节点创建一个新的节点。

setup(_, ctx) {
 const originRef = ref<HTMLElement>();
 const panelRef = ref<HTMLElement>();
 const panelStyle = ref<CSSProperties>({ position: 'absolute' });
 // ...
 return () => (
  <>
   <div ref={originRef}>origin</div>

   <Teleport to="#cdk-overlay-anchor">
     <div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, height: '100vh', width: '100vw' pointerEvents: 'none'}}>
      <div ref={panelRef} style={panelStyle.value}>
       <div style={{height: '100px', width: '100px' border: '1px solid black'}} />
      </div>
     </div>
   </Teleport>
  </>
 );
}

拿到这两者的 dom ref 后,需要通过实时计算 Origin 的盒子的大小和位置,来获得 Panel 的相对偏移。在 Vue 中,元素只有在 mounted 后才能获取得到,所以可以通过 composition-api 的 onMouted 来获取具体元素。然后再在 生命周期中 进行计算。

计算两个盒子的相对位置

如何计算 Origin 的大小和位置,以及获取其变化后的监听。Origin 的大小和位置,通过 getBoundingClientRect 这一 API 来获取,这一就可以开始计算 Overlay 的相对位置。假设我们要把 Overlay 放在 Origin 的正下方,计算函数应该是这样的。

const panelStyle = ref<CSSProperties>({ position: 'absolute' });

onMounted(() => {
 const origin = originRef.value;
 const panel = panelRef.value;
 if (!origin || !panel) {
  return ;
 }

 const calculate = () => {
  const rect = origin.getBoundingClientRect();
  // 原点为 origin 元素的底边中央正下方
  const originX = rect.left + (rect.width / 2);
  const originY = rect.bottom;

  // panel的坐标为到原点的偏移
  const panelRect = panel.getBoundingClientRect();
  const panelX = originX - panelRect.width / 2;
  const panelY = originY;

  // 设置 panel 数据,触发节点变更
  panelStyle.value.left = `${panelX}px`;
  panelStyle.value.top = `${panelY}px`;
 };
});

当然,你还可以计算各个不同方向的 Panel 坐标(比如,正左、正上、正下等),排列组合一下,一共有种27不同的情况(每个点依赖于两个变量 X 和 Y;每个变量有三种不同的情况,左、中、右,或者,上、中、下)。

监听盒子的变化

在这里,我们将使用浏览器自带的API 来对他们进行监听。通过 MutationObserver 和 ResizeObserver,可以很轻松的监听 Origin 和 Panel 的大小和位置变化。

首先是监听 Origin 的大小和位置变化,这里采用的是 MutationObserver,因为导致位置变化的原因只能是 style,所以只需要监听 style 的变化即可。

const origin$ = new MutationObserver(calculate);
origin$.observe(origin, {
 // 只需要拿到 attribute 的 style 的变化即可
 attributeFilter: ['style'],
});

Panel 只需要监听其大小的变化,大小变化有一个更加完美的API, ResizeObserver。

const panel$ = new ResizeObserver(calculate);
panel$.observe(panel);

然后,需要在dom销毁前取消监听。

// dom销毁前取消监听
onBeforeUnmount(() => {
 origin$.disconnect();
 panel$.disconnect();
});

监听窗口事件

为了能够正确的获取变化,我们需要监听两个事件:resize 和 scroll.

// 为了能够在滚动事件捕获前进行计算,带有滚动条的子元素也会因此触发计算
window.addEventListener('scroll', calculate, true);
window.addEventListener('resize', calculate);
最后,仍然要在销毁前取消事件。

// dom销毁前取消监听
onBeforeUnmount(() => {
 window.removeEventListener('scroll', calculate, true);
 window.removeEventListener('resize', calculate);
});

至此,已经完成基本的双盒子定位法的 Overlay 的设计。

小结

通过双盒子定位来构建的 Overlay 能够有效规避 CSS 带来的问题 zindex 等一系列相关的问题,只用通过计算盒子之间的相对偏移,就能让 Panel 附着于 Origin 上,这样,实现类似下拉或者 Tooltip 等功能的时候,就会非常有用。同时,附上一个简单例子,希望能带来一些启发。

以上就是Vue3 实现双盒子定位Overlay的示例的详细内容,更多关于vue3 实现Overlay的资料请关注三水点靠木其它相关文章!

Vue.js 相关文章推荐
vue+elementUI动态增加表单项并添加验证的代码详解
Dec 17 Vue.js
如何在vue中使用kindeditor富文本编辑器
Dec 19 Vue.js
Vue 修改网站图标的方法
Dec 31 Vue.js
vue项目如何监听localStorage或sessionStorage的变化
Jan 04 Vue.js
vue二选一tab栏切换新做法实现
Jan 19 Vue.js
Vue实现todo应用的示例
Feb 20 Vue.js
vue实现倒计时功能
Mar 24 Vue.js
Vue详细的入门笔记
May 10 Vue.js
一定要知道的 25 个 Vue 技巧
Nov 02 Vue.js
Vue.js中v-for指令的用法介绍
Mar 13 Vue.js
vue使用refs获取嵌套组件中的值过程
Mar 31 Vue.js
Vue 打包后相对路径的引用问题
Jun 05 Vue.js
详解Vue的异步更新实现原理
Dec 22 #Vue.js
Vue组件简易模拟实现购物车
Dec 21 #Vue.js
vue实现购物车的小练习
Dec 21 #Vue.js
Vue实现小购物车功能
Dec 21 #Vue.js
vue监听滚动事件的方法
Dec 21 #Vue.js
vue el-upload上传文件的示例代码
Dec 21 #Vue.js
vue 在单页面应用里使用二级套嵌路由
Dec 19 #Vue.js
You might like
SONY SRF-M100的电路分析
2021/03/02 无线电
phpMyAdmin链接MySql错误 个人解决方案
2009/12/28 PHP
windows下安装php的memcache模块的方法
2015/04/07 PHP
PHP实现RTX发送消息提醒的实例代码
2017/01/03 PHP
详解如何在云服务器上部署Laravel
2017/06/30 PHP
PHP简单获取上月、本月、近15天、近30天的方法示例
2017/07/03 PHP
Javascript load Page,load css,load js实现代码
2010/03/31 Javascript
html组件不可输入(只读)同时任何组件都有效
2013/04/01 Javascript
JavaScript常用脚本汇总(二)
2015/03/04 Javascript
javascript+html5实现仿flash滚动播放图片的方法
2015/04/27 Javascript
使用AngularJS中的SCE来防止XSS攻击的方法
2015/06/18 Javascript
利用原生js和jQuery实现单选框的勾选和取消操作的方法
2016/09/04 Javascript
jQuery实现ToolTip元素定位显示功能示例
2016/11/23 Javascript
ExtJs异步无法向外传值和赋值的完美解决办法
2017/06/14 Javascript
小程序云开发实战小结
2018/10/25 Javascript
jquery获取并修改触发事件的DOM元素示例【基于target 属性】
2019/10/10 jQuery
微信小程序实现星级评价
2019/11/20 Javascript
解决Vue.js应用回退或刷新界面时提示用户保存修改问题
2019/11/24 Javascript
Vue实现背景更换颜色操作
2020/07/17 Javascript
js实现类选择器和name属性选择器的示例步骤
2021/02/07 Javascript
[59:15]EG vs LGD 2018国际邀请赛淘汰赛BO3 第一场 8.26
2018/08/29 DOTA
python通过BF算法实现关键词匹配的方法
2015/03/13 Python
利用Python操作消息队列RabbitMQ的方法教程
2017/07/19 Python
Python实现自定义函数的5种常见形式分析
2018/06/16 Python
Django框架搭建的简易图书信息网站案例
2019/05/25 Python
python识别图像并提取文字的实现方法
2019/06/28 Python
详解用Python为直方图绘制拟合曲线的两种方法
2019/08/21 Python
Python语法垃圾回收机制原理解析
2020/03/25 Python
ALDO美国官网:加拿大女鞋品牌
2018/12/28 全球购物
亚马逊新加坡官方网站:Amazon.sg
2020/03/25 全球购物
.NET程序员的数据库面试题
2012/10/10 面试题
优秀毕业生找工作自荐信
2014/06/23 职场文书
师德师风学习材料
2014/12/19 职场文书
公文写作:新员工转正申请书范本3篇!
2019/08/07 职场文书
四年级作文之说明文作文
2019/10/14 职场文书
使用python绘制横竖条形图
2022/04/21 Python