js flyout 2: VScroll

news2025/1/9 15:03:50

目录

  • 版权
  • 描述
  • 测试页面
  • showFlyout
  • 问题1 - scroll 实现可能不准?
  • 问题2 - 容器内容重排可导致浮层错位
    • 关于重排
  • 小结
  • 附录 - 完整代码

版权

本文为原创, 遵循 CC 4.0 BY-SA 版权协议, 转载需注明出处: https://blog.csdn.net/big_cheng/article/details/130101031.
文中代码属于 public domain (无版权).

描述

上一篇博文 js flyout 介绍了浮层, 实践中发现浮层弹出时如果超出范围会导致容器出现滚动条. 本文研究如何自动消除VScroll (希望页面整洁, 只有body 有VScroll).

首先重整一下原来的代码. 在head 定义样式:

    <style>
        .hide {display: none;}

        ._flyout {position: absolute; padding: 10px;
            border: 1px solid lightslategray; border-radius: 8px;}
    </style>

定义一个fw 对象, 封装一些基础功能:

    <script>
        var fw = {
            noop() {return false; },

            get(id) {return document.getElementById(id); },

            show(id) {fw.get(id).classList.remove("hide"); },

            hide(id) {fw.get(id).classList.add("hide"); }
        };

显示/隐藏通过操纵"hide" class 实现.
将Flyout 移入fw 对象并稍做调整:

        /** Flyout 行为: OutsideClick/ESC => 隐藏. */
        fw.decorateFlyout = function(id, onHide) {
            var ele = fw.get(id);
            ele.onHide = (onHide || fw.noop);

            var fnH = function() {
                window.removeEventListener('click', fnH); // del hook
                fw.hide(id);
                ele.onHide();
            };
            ele.onkeydown = function(evt) {if (evt.keyCode == 27) fnH() }; // ESC
            ele.onclick = function(evt) {evt.stopPropagation() }; // 屏蔽InsideClick

            ele.fShow = function() {
                window.removeEventListener('click', fnH); //(可能连续fShow, 先del old hook)
                fw.show(id);
                window.setTimeout(function() { // add hook, 需异步
                    window.addEventListener('click', fnH);
                }, 100);
            };
            ele.fHide = fnH;
        };

主要修改是将onHide 暴露为ele 的一个属性, 便于在隐藏时增加逻辑.

测试页面

测试页面效果图:
在这里插入图片描述
容器加黑框, 内有文字/按钮等, 浮层也定义在此容器内. 点按钮显示浮层时, 如果浮层高度超出容器下边缘会导致容器自动出现VScroll. 页面代码:

<body>
    <a href="xxx">another link</a>
    <p></p>

    <style>
        .ct {border: 1px solid; min-height: 200px; overflow-x: auto;
            position: relative;}
        #ov {width: 110%; border: 1px solid blue;}
        #f2 {white-space: nowrap; height: 50px;}
    </style>
    <div id="ct" class="ct">
        A brown fox quickly jump over a fence.
        <br>
        <div id="ov">
            overflow content
        </div>
        
        <p></p>
        <button onclick="ov.innerText += ov.innerText;">Add ov</button>
        <p></p>
        <button onclick="showFlyout(f1, event, 0, 0);">Show f1</button>
        <button id="btn2" onclick="showFlyout(f2, event, 40, 20);">Show f2</button>

        <div id="f1" class="_flyout hide">
            flyout content: f1
        </div>
        <script> fw.decorateFlyout("f1"); </script>

        <div id="f2" class="_flyout hide">
            flyout content: f2
        </div>
        <script> fw.decorateFlyout("f2"); </script>

    </div><!-- ct -->

</body>
</html>

子容器ov 宽度110% 导致容器有HScroll.

showFlyout

由于常常需要相对鼠标点击位置来显示浮层, 增加一个showFlyout 方法:

        // 显示flyout.
        function showFlyout(fly, evt, dx, dy) {
            var v = evt.pageX - ct.offsetLeft + ct.scrollLeft + dx;
            if (v < 1) v = 1;
            fly.style.left = v+"px";
            v = evt.pageY - ct.offsetTop + ct.scrollTop + dy;
            if (v < 1) v = 1;
            fly.style.top = v+"px";
            fly.fShow();

鼠标事件的pageX/pageY 是相对于document 的左上角. 例如查到测试页里容器距离document 上边缘 = ct.offsetTop = 46px, 要换算成相对于容器左上角的坐标, 需要减去此距离.
如果容器目前有VScroll 且部分内容滚出了容器上边缘(高度即ct.scrollTop), 要换算成相对于容器左上角的坐标, 需要加上此高度.

设置了浮层左上角坐标并显示出来后, 如果浮层高度超出容器下边缘会导致容器出现VScroll. 例如点击"Show f2" 按钮(右边缘处) 效果图:
在这里插入图片描述
参考"css scroll 上手试验" 博文的"要消除VScroll, 还需考虑HScroll" 小节, 要消除容器VScroll 需要设置高度 = scrollHeight + HScroll的高度:

        // 显示flyout.
        function showFlyout(fly, evt, dx, dy) {
            ......

            // 消除VScroll
            if (ct.scrollHeight > ct.clientHeight) {
                var oldV = ct.style.minHeight;
                ct.style.minHeight =
                    (ct.scrollHeight + (ct.offsetHeight - 2 - ct.clientHeight))+"px";
                var oldOnH = fly.onHide;
                fly.onHide = function() {
                    ct.style.minHeight = oldV;
                    fly.onHide = oldOnH;
                    oldOnH();
                };
            }
        }
    </script>

scrollHeight > clientHeight 判断为有VScroll.
offsetHeight - 2:容器上下边框线 - clientHeight = HScroll的高度.
注意这里要设置"min-height" 而非"height", 因为容器的(文字)内容重排(例如用户压缩屏幕宽度时) 可能导致纵向溢出, 如果高度固定则又会出现VScroll.

如果修改了高度, 那么也修改ele.onHide: 在调用原有onHide 之前自动恢复修改前的高度.

现在f2 浮层出现后容器自动增高, 不出现VScroll; 浮层隐藏后容器高度又恢复.

问题1 - scroll 实现可能不准?

从上往下逐渐点击"Show f2" 按钮的右边缘, 偶尔会出现几个问题(Chrome 112.0.5615.50, 64 位):
a) scrollHeight == clientHeight 但出现了VScroll, 而且内容可以滚动约1px.
b) scrollHeight == clientHeight 也没有VScroll, 但浮层下边框线未显示.

效果图:
在这里插入图片描述
将"min-height" 调高(1px或2px):

                ct.style.minHeight =
                    (ct.scrollHeight + (ct.offsetHeight - 2 - ct.clientHeight) + 1)+"px";

问题均仍可能出现. 甚至加到5 后仍观察到b) 现象. 改成异步调高也不解决.

“css scroll 上手试验” 博文里也观察到较明确的scroll 实现可能不准的现象.

问题2 - 容器内容重排可导致浮层错位

屏宽调到261px: 容器(ct) 含框宽245 = 261 - 16:body margin. 子容器(ov) 含框宽269 = (245 - 2:左右边框线) * 110% + 2:子容器左右边框线 = 243*1.1 + 2.

点击"Add ov" 按钮4次 => 再点击"Show f2", 效果对比图如下:
在这里插入图片描述
浮层f2 显示(fly.fShow() ) 后, 子容器发生内容重排撑高了ct 容器, 导致f2 相对于按钮本应在下变成了在上. 在判断是否有VScroll (“if (ct.scrollHeight > ct.clientHeight) {”) 这一行加断点, 执行到这里暂停可以看到当时的界面就等于最终效果图的界面.

关于重排

刷新页面, 调整ct 的样式: 去掉"min-height", 增加"height: 100px;". 查到ov 含框宽是251:
在这里插入图片描述
(244.84:容器宽 - 2 - 16:VScroll)*1.1 + 2 = 251.524. 推测是这样:
a) fw.show 显示f2 (在按钮下方 - 纵向空间不够)
b) ct 出现VScroll
c) ov 110% 的基数要减去VScroll, ov 变窄 => 发生重排
d) 按钮被挤到f2 下方, 发生错位

最终出现一个类似"问题1" 的情况: scrollHeight == clientHeight 但出现了VScroll - 但disable了/不可滚动.

尝试在判断是否有VScroll (“if (ct.scrollHeight > ct.clientHeight) {”) 后面增加else 分支: 写死设置minHeight = 效果图里最终的高. 结果VScroll 确实消失 => ov 110% 的基数变大, 重排回去! => 按钮返回浮层上方, 不再错位 => 容器在浮层下方多出一段空间:
在这里插入图片描述
重排后再调整高度可能触发再次重排, 这种情况下如何消除VScroll 需要再探索.

小结

目前还不完善. 浮层、高度、屏宽都会影响重排.

在布局能阻止重排(例如ov 是富文本编辑器 - 高度固定了) 时, 本文的方法可用.

附录 - 完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>flyout2</title>
    <style>
        .hide {display: none;}

        ._flyout {position: absolute; padding: 10px;
            border: 1px solid lightslategray; border-radius: 8px;}
    </style>
    <script>
        var fw = {
            noop() {return false; },

            get(id) {return document.getElementById(id); },

            show(id) {fw.get(id).classList.remove("hide"); },

            hide(id) {fw.get(id).classList.add("hide"); }
        };

        /** Flyout 行为: OutsideClick/ESC => 隐藏. */
        fw.decorateFlyout = function(id, onHide) {
            var ele = fw.get(id);
            ele.onHide = (onHide || fw.noop);

            var fnH = function() {
                window.removeEventListener('click', fnH); // del hook
                fw.hide(id);
                ele.onHide();
            };
            ele.onkeydown = function(evt) {if (evt.keyCode == 27) fnH() }; // ESC
            ele.onclick = function(evt) {evt.stopPropagation() }; // 屏蔽InsideClick

            ele.fShow = function() {
                window.removeEventListener('click', fnH); //(可能连续fShow, 先del old hook)
                fw.show(id);
                window.setTimeout(function() { // add hook, 需异步
                    window.addEventListener('click', fnH);
                }, 100);
            };
            ele.fHide = fnH;
        };

        // 显示flyout.
        function showFlyout(fly, evt, dx, dy) {
            var v = evt.pageX - ct.offsetLeft + ct.scrollLeft + dx;
            if (v < 1) v = 1;
            fly.style.left = v+"px";
            v = evt.pageY - ct.offsetTop + ct.scrollTop + dy;
            if (v < 1) v = 1;
            fly.style.top = v+"px";
            fly.fShow();

            // 消除VScroll
            if (ct.scrollHeight > ct.clientHeight) {
                var oldV = ct.style.minHeight;
                ct.style.minHeight =
                    (ct.scrollHeight + (ct.offsetHeight - 2 - ct.clientHeight) + 0)+"px";
                var oldOnH = fly.onHide;
                fly.onHide = function() {
                    ct.style.minHeight = oldV;
                    fly.onHide = oldOnH;
                    oldOnH();
                };
            } else {
                // ct.style.minHeight = "465px"; // 写死
            }
        }
    </script>
</head>
<body>
    <a href="xxx">another link</a>
    <p></p>

    <style>
        .ct {border: 1px solid; min-height: 200px; overflow-x: auto;
            position: relative;}
        #ov {width: 110%; border: 1px solid blue;}
        #f2 {white-space: nowrap; height: 50px;}
    </style>
    <div id="ct" class="ct">
        A brown fox quickly jump over a fence.
        <br>
        <div id="ov">
            overflow content
        </div>
        
        <p></p>
        <button onclick="ov.innerText += ov.innerText;">Add ov</button>
        <p></p>
        <button onclick="showFlyout(f1, event, 0, 0);">Show f1</button>
        <button id="btn2" onclick="showFlyout(f2, event, 40, 20);">Show f2</button>

        <div id="f1" class="_flyout hide">
            flyout content: f1
        </div>
        <script> fw.decorateFlyout("f1"); </script>

        <div id="f2" class="_flyout hide">
            flyout content: f2
        </div>
        <script> fw.decorateFlyout("f2"); </script>

    </div><!-- ct -->

</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/417695.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【致敬未来的攻城狮计划】学习总结

文章目录【致敬未来的攻城狮计划】学习总结前言学习总结一、RT-Thread二、RA2E1开发板三、学习移植RT-Thread四、学习RT-Thread设备五、其他收获六、总结【致敬未来的攻城狮计划】学习总结 &#x1f680;&#x1f680;开启攻城狮的成长之旅&#xff01;这是我参与的由 CSDN博客…

【RabbitMQ学习日记】——死信队列与延迟队列

一、死信队列 1.1 相关概念 死信&#xff0c;顾名思义就是无法被消费的消息&#xff0c;字面意思可以这样理解&#xff0c;一般来说&#xff0c;producer 将消息投递到 broker 或者直接到 queue 里了&#xff0c;consumer 从 queue 取出消息进行消费&#xff0c;但某些时候由…

云擎未来,智信天下 | 2023移动云大会来了!

新三年&#xff0c;新征程 2023年作为新三年开局之年 移动云又将以怎样的 全新品牌形象、全新战略规划 向“一流云服务商”战略目标勇毅前行&#xff1f; 答案就在这里&#xff1a; 2023移动云大会&#xff0c;官宣定档&#xff01; 2023.4.25 - 4.26 苏州金鸡湖国际会…

MATLAB配置C/C++库(Visual Studio,MinGW-w64 C/C++ 编译器)问题(包括低版本matlab配置高版本VS)

问题描述 使用matlab加载C语言的库函数时&#xff0c;需要提前配置好C/C编译器&#xff0c;否则在matlab中使用 loadlibrary 加载C /C库中的函数时候&#xff0c;会报错&#xff1a; “未找到支持的编译器或 SDK。您可以安装免费提供的 MinGW-w64 C/C 编译器&#xff1b;请参…

软考第三章 广域通信网

广域通信网 1.公共交换电话网 公共交换电话网PSTN&#xff1a;是为了话音通信而建立的网络&#xff0c;在有些地方用户仍然通过电话线拨号上网 1.1 电话系统的结构 电话系统是一个高度冗余的分级网络。用户电话通过一对铜线连接到最近的端局。 公共电话网由本地网和长途网组…

一文速学数模-最优化算法(二)梯度下降算法一文详解+Python代码

目录 前言 一、梯度下降法简述 二、梯度下降算法原理理解 1.梯度 2.梯度定义 3.梯度下降 4.损失函数(loss function) 5.学习率(步长) 三、梯度下降算法代码展示 消失和爆炸梯度 前言 最近会不断更新深度学习系列文章(全实战性可运行代码)加入到我的一文速学-数学建模…

Git项目同时推送到GitHub和Gitee详细操作

文章目录前言一、创建仓库【Create a new repository】二、初始化三、配置公钥四、密钥验证五、代码推送总结前言 将Git项目同时推送到GitHub和Gitee的好处如下&#xff1a; 提高代码可见性和协作性&#xff1a;GitHub和Gitee都是知名的代码托管平台&#xff0c;推送代码到这两…

大数据能力提升项目|学生成果展系列之五

导读为了发挥清华大学多学科优势&#xff0c;搭建跨学科交叉融合平台&#xff0c;创新跨学科交叉培养模式&#xff0c;培养具有大数据思维和应用创新的“π”型人才&#xff0c;由清华大学研究生院、清华大学大数据研究中心及相关院系共同设计组织的“清华大学大数据能力提升项…

JavaScript【十】JavaScript事件

文章目录&#x1f31f;前言&#x1f31f;事件&#x1f31f;绑定事件的方式&#xff1a;&#x1f31f;标签绑定事件&#xff1a;&#x1f31f;Document对象来绑定事件&#xff1a;on事件type&#x1f31f; 事件监听:使同一个对象的同一事件绑定多个事件处理程序。兼容IE9及以上。…

Zephyr RTOS应用开发(nrf5340)

目录 概述 开发环境安装 创建一个新的Zephyr应用 构建应用并刷写到开发板 概述 Zephyr™项目是一个采用Apache 2.0协议许可&#xff0c;Linux基金会托管的协作项目。针对低功耗、小型内存微处理器设备开发的物联网嵌入式小型、可扩展的实时操作系统&#xff0c;支持多种硬件…

redis哨兵机制详解

文章目录前言监控&#xff08;Monitoring&#xff09;自动故障转移&#xff08;Automatic failover&#xff09;配置提供者&#xff08;Configuration provider&#xff09;通知&#xff08;Notification&#xff09;哨兵集群的组建哨兵监控Redis库主库下线的判定主观下线客观下…

ORB-SLAM2原理分析

原理分析 ORB-SLAM2是一种基于单目、双目和RGB-D相机的实时视觉SLAM系统&#xff0c;用于在无GPS信号或有限的传感器信息情况下&#xff0c;构建三维地图并定位相机的位置和姿态。ORB-SLAM2采用了ORB特征点提取和描述符匹配技术&#xff0c;以及图优化和闭环检测算法&#xff…

分布式系统监控zabbix安装部署及使用

目录 一、zabbix监控 1、什么是zabbix 2、zabbix功能 3、zabbix运行机制 4、zabbix的3种架构 ①C/S架构 ②zabbix-proxy-client架构 ③master-zabbix-client架构 5、zabbix工作原理及数据走向 6、zabbix监控模式 7、zabbix部署 8、zabbix图形化页面显示设置 二、Z…

技术复盘(3)--ElasticSearch

技术复盘--ElasticSearch技术复盘(3)--ElasticSearch资料地址概述对比solrwindows下使用esIK分词器介绍es基本命令集成springboot以及调用api技术复盘(3)–ElasticSearch ElasticSearch7.x 资料地址 ElasticSearch官网&#xff1a;https://www.elastic.co/ ElasticSearch-he…

unity3d:网络同步,状态同步,源码,C#服务器demo

协议数据单元 网络同步包最小单元PDU // 预测的基础数据类型 public class PDU { public uint UID; //玩家的唯一id public PDUType type; //PDU类型 public Vector3 position; // 位置 public Vector3 forward; // 朝向 public float speed; // 速度: 速度为…

【STL十一】无序容器(哈希容器)—— unordered_map、unordered_set

【STL十一】无序容器&#xff08;哈希容器&#xff09;—— unordered_map、unordered_set一、简介1、关联容器和无序容器不同2、无序容器特点二、头文件三、模板类四、无序容器的内部结构1、管理桶2、内部结构五、unordered_map成员函数1、迭代器2、元素访问3、容量4、修改操作…

CV大模型应用:Grounded-Segment-Anything实现目标分割、检测与风格迁移

Grounded-Segment-Anything实现目标分割、检测与风格迁移 文章目录Grounded-Segment-Anything实现目标分割、检测与风格迁移一、Segment-Anything介绍二、Grounded-Segment-Anything1、简介2、测试一、Segment-Anything介绍 代码链接&#xff1a;https://github.com/facebookr…

Direct3D 12——混合——雾

实现雾化效果的流程如下&#xff1a;如图所示&#xff0c;首先指明雾的颜色、由摄像机到雾气的最近距离以及雾 的分散范围(即从雾到摄像机的最近距离至雾能完全覆盖物体的这段范围)&#xff0c;接下来再将网格三角形上点 的颜色置为原色与雾色的加权平均值&#xff1a; foggedC…

Python爬虫之多线程加快爬取速度

之前我们学习了动态翻页我们实现了网页的动态的分页&#xff0c;此时我们可以爬取所有的公开信息了&#xff0c;经过几十个小时的不懈努力&#xff0c;一共获取了 16万 条数据&#xff0c;但是软件的效率实在是有点低了&#xff0c;看了下获取 10 万条数据的时间超过了 56 个小…

【技巧】Word“只读方式”的设置与取消

如果你担心在阅读Word文档的时候&#xff0c;不小心修改并保存了内容&#xff0c;那就给文档设置“只读方式”吧&#xff0c;这样就算不小心做了修改也不能随意保存。 Word文档的“只读方式”有两种模式&#xff0c;对此不清楚的小伙伴&#xff0c;来看看如何设置和取消吧。 模…