xmlplus组件设计系列之分隔框(DividedBox)(8)


Posted in Javascript onMay 02, 2017

分隔框(DividedBox)是一种布局类组件,可以分为两类,其中一类叫水平分隔框(HDividedBox),另一类叫垂直分隔框(VDividedBox)。水平分隔框会将其子级分为两列,而垂直分隔框则会将其子级分为两行。列与列之间以及行与行之间一般都会有一条可以拖动的用以改变子级组件大小的分隔条。下面仅以垂直分隔框为例来介绍此类组件是如何设计以及实现的。

成品组件用例

按照以往的设计经验,我们可以先写出想像中的成品组件用例,这将有助于我们后续的进一步的设计与实现。垂直分隔框既然是布局类的组件,那么它也一定是一个容器,该容器包含了上述我们提到的三种子级组件。为了使用方便,我们不应该把分隔框也写进去,分隔框应该由组件内部实现的。经过分析,我们得到下面的一个应用示例:

Example1: {
 css: "#example div { width: 80%; height: 80%; background: #AAA; }",
 xml: `<VDividedBox id="example">
    <div id='top'/>
    <div id='bottom'/>
   </VDividedBox>`
}

该示例由一垂直分隔框组件包裹着两个 div 元素。这里分别设置两个 div 元素的宽高为父级的 80%,同时设置它们的背景色为灰色,这只是为了方便测试。另外,我们还需要考虑一个子框的初始比例分配问题。我们可以设置默认比例为 50:50,比例最好可以在组件实例化时静态指定,同时提供比例设置的动态接口。于是我们就有了下面的改进用例。

Example2: {
 css: "#example div { width: 80%; height: 80%; background: #AAA; }",
 xml: `<VDividedBox id="example" percent='30'>
    <div id='top'/>
    <div id='bottom'/>
   </VDividedBox>`,
 fun: function (sys, items, opts) {
  sys.top.on("click", e => sys.example.percent = 50);
 }
}

这个用例在垂直分隔框初始化时设置子框的初始比例分配为 30:70,当用户点击第一子框时,比例分配重新恢复为 50:50。不过要注意,这些比例分配指的是对排除分隔条所占用空间后剩余空间的比例分配。

设计与实现

现在让我们把注意力转移到组件的内部。我们先大致地确定组件基本的组成。直观地看,垂直分隔框显示包含三个组件部分:上子框部分、分隔条以及下子框部分。于是我们暂时可以得到下面的视图项部分:

VDividedBox: {
 xml: `<div id='hbox'>
   <div id='top'/>
   <div id='handle'/>
   <div id='bottom'/>
   </div>`
}

下一步,确保垂直分隔框组件实例的子级部分被正确地映射到上子框 top 以及下子框 bottom。方法是先让所有的子级元素对象全部被添加到上子框 top 中,然后在函数项中将下子级元素添加到下子框 bottom 中。

VDividedBox: {
 xml: `<div id='hbox'>
   <div id='top'/>
   <div id='handle'/>
   <div id='bottom'/>
   </div>`,
 map: {appendTo: "top" },
 fun: function (sys, items, opts) {
  sys.bottom.elem().appendChild(this.last().elem());
 }
}

现在让我们来考虑下视图项的样式,对于顶层 div 元素,我们设置其定位方式为相对定位。对于子级的三个元素则设置为绝对定位。另外,把分隔条高度设置为 5px。

VDividedBox: {
 css: `#hbox { position:relative; width:100%; height:100%; box-sizing: border-box; }
   #top { top: 0; height: 30%; } #bottom { bottom: 0; height: calc(70% - 5px); }
   #top,#bottom { left: 0; right: 0; position: absolute; }
   #handle { height: 5px; width: 100%; position:absolute; left:0; top: 30%; z-index:11; cursor:row-resize; }`,
 xml: `<div id='hbox'>
   <div id='top'/>
   <div id='handle'/>
   <div id='bottom'/>
   </div>`,
 map: {appendTo: "top" },
 fun: function (sys, items, opts) {
  sys.bottom.elem().appendChild(this.last().elem());
 }
}

最后让我们看看如何响应分隔条的拖动事件,从而更改子框的分配比例。我们需要定义一个改变子框比例的函数,同时侦听分隔条的拖拽事件。下面是我们的一个实现。

VDividedBox: {
 // 视图项同上
 map: { format: {"int": "percent"}, appendTo: "top" }, 
 fun: function (sys, items, opts) {
  var percent = 50;
  sys.handle.on("dragstart", function (e) {
   sys.hbox.on("dragover", dragover);
  });
  sys.hbox.on("dragend", function (e) {
   e.stopPropagation();
   sys.hbox.off("dragover", dragover);
  });
  function dragover(e) {
   e.preventDefault();
   setPercent((e.pageY - sys.hbox.offset().top) / sys.hbox.height() * 100);
  }
  function setPercent(value) {
   sys.handle.css("top", value + "%");
   sys.top.css("height", value + "%");
   sys.bottom.css("height", "calc(" + (100 - value) + "% - 5px)");
  }
  setPercent(opts.percent || percent);
  sys.bottom.elem().appendChild(this.last().elem());
  return Object.defineProperty({}, "percent", {get: () => {return percent}, set: setPercent});
 }
}

上述代码的映射项中有一项关于 percent 格式的设置,该设置确保了 percent 为整型数。另外函数项中对子框的比例设定用到了 css3 的 calc 计算函数,改函数在浏览器窗体改变大小时仍然能够起作用。如果你希望兼容更多的浏览器,你需要做更多的工作。另外注意,为了让组件有好的性能表现,只有当用户开始拖拽时,才对事件 dragover 实施侦听。

进一步改进

让我们现在做个小测试,写一个包含两个文本域作为子级的垂直分隔框的应用实例。拖动分隔条,看会出现什么结果。

Example3: {
 css: `#example textarea { width: 80%; height: 80%; }`,
 xml: `<VDividedBox id="example">
    <textarea id='top'/>
    <textarea id='bottom'/>
   </VDividedBox>`
}

在这个示例中,有时候分隔条会失灵,子框比例不再随着分隔条位置而出现变化。问题出在文本域对拖拽事件进行了劫持,导致我们我组件内部收不到响应的事件。我们需要做些补丁才行。

VDividedBox: {
 css: "#hbox { position:relative; width:100%; height:100%; box-sizing: border-box; }\
   #top { top: 0; height: 30%; } #bottom { bottom: 0; height: calc(70% - 5px); }\
   #top,#bottom { left: 0; right: 0; position: absolute; }\
   #handle { height: 5px; width: 100%; position:absolute; left:0; top: 30%; z-index:11; cursor:row-resize; }\
   #mask { width: 100%; height: 100%; position: absolute; display: none; z-index: 10; }",
 xml: "<div id='hbox'>\
   <div id='top'/>\
   <div id='handle' draggable='true'/>\
   <div id='bottom'/>\
   <div id='mask'/>\
   </div>",
 map: { format: {"int": "percent"}, appendTo: "top" }, 
 fun: function (sys, items, opts) {
  var percent = 50;
  sys.handle.on("dragstart", function (e) {
   sys.mask.show();
   sys.hbox.on("dragover", dragover);
  });
  sys.hbox.on("dragend", function (e) {
   sys.mask.hide();
   e.stopPropagation();
   sys.hbox.off("dragover", dragover);
  });
  function dragover(e) {
   e.preventDefault();
   setPercent((e.pageY - sys.hbox.offset().top) / sys.hbox.height() * 100);
  }
  function setPercent(value) {
   sys.handle.css("top", value + "%");
   sys.top.css("height", value + "%");
   sys.bottom.css("height", "calc(" + (100 - value) + "% - 5px)");
  }
  setPercent(opts.percent || percent);
  sys.bottom.elem().appendChild(this.last().elem());
  return Object.defineProperty({}, "percent", {get: () => {return percent}, set: setPercent});
 }
}

为了解决问题,我们在组件中引用了额外的 div 元素对象 mask,此元素默认是不显示的。当拖动开始时,它才会覆盖住子框以及分隔条,而拖动一结束,它又隐藏掉。这样就避免了文本域对拖拽事件的劫持。

结合水平分隔框使用

我们有了上述垂直分隔框的设计经验,搞个水平分隔框也就不是什么难事了,这里就不列出来了。这里主要是给出一个综合使用水平分隔框和垂直分隔框的示例。当然的设计之初,我们并没有想到要这么使用。

Example4: {
 css: `#example div { width: 100%; height: 100%; }`,
 xml: `<HDividedBox id='example'>
    <VDividedBox percent='30'>
     <div/><div/>
    </VDividedBox>
    <VDividedBox percent='30'>
     <div/><div/>
    </VDividedBox>
   </HDividedBox>`
}

这个示例主要用于展示当分隔框嵌套使用时的表现。该示例包含一个水平分隔框,该水平分隔框又包含两个垂直分隔框,这种布局在不少编辑器中是很常见的,我们这里已经简单高效地把它实现了。

本系列文章基于 xmlplus 框架。如果你对 xmlplus 没有多少了解,可以访问 www.xmlplus.cn。这里有详尽的入门文档可供参考。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Javascript获取窗口(容器)的大小及位置参数列举及简要说明
Dec 09 Javascript
js变换显示图片的实例
Apr 16 Javascript
Javascript call和apply区别及使用方法
Nov 14 Javascript
纯js实现仿QQ邮箱弹出确认框
Apr 29 Javascript
教你使用javascript简单写一个页面模板引擎
May 05 Javascript
使用JavaScript制作一个简单的计数器的方法
Jul 07 Javascript
jquery层级选择器的实现(匹配后代元素div)
Sep 05 Javascript
js放到head中失效的原因与解决方法
Mar 07 Javascript
bootstrap+jQuery实现的动态进度条功能示例
May 25 jQuery
vue中导出Excel表格的实现代码
Oct 18 Javascript
vue+element-ui JYAdmin后台管理系统模板解析
Jul 28 Javascript
JavaScript this关键字指向常用情况解析
Sep 02 Javascript
xmlplus组件设计系列之树(Tree)(9)
May 02 #Javascript
详解Vue2.X的路由管理记录之 钩子函数(切割流水线)
May 02 #Javascript
令按钮悬浮在(手机)页面底部的实现方法
May 02 #Javascript
Vue2.0表单校验组件vee-validate的使用详解
May 02 #Javascript
ES6学习教程之对象的扩展详解
May 02 #Javascript
Javascript ES6中数据类型Symbol的使用详解
May 02 #Javascript
Bootstrap实现基于carousel.js框架的轮播图效果
May 02 #Javascript
You might like
社区(php&amp;&amp;mysql)三
2006/10/09 PHP
怎样在php中使用PDF文档功能
2006/10/09 PHP
利用curl 多线程 模拟 并发的详解
2013/06/14 PHP
如何在smarty中增加类似foreach的功能自动加载数据
2013/06/26 PHP
Laravel 5.4因特殊字段太长导致migrations报错的解决
2017/10/22 PHP
Yii2结合Workerman的websocket示例详解
2018/09/10 PHP
PHP面向对象程序设计之对象克隆clone和魔术方法__clone()用法分析
2019/06/12 PHP
php设计模式之模板模式实例分析【星际争霸游戏案例】
2020/03/24 PHP
Javascript中Eval函数的使用
2010/03/23 Javascript
jQuery1.5.1 animate方法源码阅读
2011/04/05 Javascript
iframe异步加载实现点击左边菜单加载右边内容实例讲解
2013/03/04 Javascript
js导出table到excel同时兼容FF和IE示例
2013/09/03 Javascript
JS实现兼容各浏览器解析XML文档数据的方法
2015/06/01 Javascript
jQuery的bind()方法使用详解
2015/07/15 Javascript
详解JavaScript对Date对象的操作问题(生成一个倒数7天的数组)
2015/10/01 Javascript
JavaScript统计网站访问次数的实现代码
2015/11/18 Javascript
BootStrap的alert提示框的关闭后再显示怎么解决
2016/05/17 Javascript
深入浅析JS Function()构造函数
2016/08/22 Javascript
JS快速实现移动端拼图游戏
2016/09/05 Javascript
快速入门Vue
2016/12/19 Javascript
微信小程序 POST请求的实例详解
2017/09/29 Javascript
JS 仿支付宝input文本输入框放大组件的实例
2017/11/14 Javascript
js如何找出字符串中的最长回文串
2018/06/04 Javascript
全面解析vue router 基本使用(动态路由,嵌套路由)
2018/09/02 Javascript
IntelliJ IDEA编辑器配置vue高亮显示
2019/09/26 Javascript
js单线程的本质 Event Loop解析
2019/10/29 Javascript
[01:07:17]EG vs Optic Supermajor 败者组 BO3 第一场 6.6
2018/06/07 DOTA
[41:13]完美世界DOTA2联赛PWL S2 Forest vs Rebirth 第一场 11.20
2020/11/20 DOTA
深入理解Django-Signals信号量
2019/02/19 Python
护理专业应届毕业生推荐信
2013/11/15 职场文书
播音主持女孩的自我评价分享
2013/11/20 职场文书
会计毕业生自荐信
2013/11/21 职场文书
初中英语演讲稿
2014/04/29 职场文书
保险专业求职信
2014/07/07 职场文书
西柏坡观后感
2015/06/08 职场文书
2019中小学生安全过暑期倡议书
2019/06/24 职场文书