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 相关文章推荐
jQuery EasyUI API 中文文档 - EasyLoader 加载器
Sep 29 Javascript
Javascript 颜色渐变效果的实现代码
Oct 01 Javascript
SeaJS 与 RequireJS 的差异对比
Dec 08 Javascript
JavaScript中通过prototype属性共享属性和方法的技巧实例
Mar 13 Javascript
JSON字符串转JSON对象
Jul 31 Javascript
JS制作类似选项卡切换的年历
Dec 03 Javascript
深入理解JavaScript创建对象的多种方式以及优缺点
Jun 01 Javascript
Node.js学习之查询字符串解析querystring详解
Sep 28 Javascript
javascript获取图片的top N主色值方法详解
Jan 26 Javascript
关于layui 弹出层一闪而过就消失的解决方法
Sep 09 Javascript
解决layui的table插件无法多层级获取json数据的问题
Sep 19 Javascript
微信小程序实现同时上传多张图片
Feb 03 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实现获取图片颜色值的方法
2014/07/11 PHP
php实现设计模式中的单例模式详解
2014/10/11 PHP
使用array_map简单搞定PHP删除文件、删除目录
2014/10/29 PHP
yii数据库的查询方法
2015/12/28 PHP
PHP  实现等比压缩图片尺寸和大小实例代码
2016/10/08 PHP
thinkPHP5.0框架独立配置与动态配置方法
2017/03/17 PHP
php 后端实现JWT认证方法示例
2018/09/04 PHP
javascript下查找父节点的简单方法
2007/08/13 Javascript
javascript showModalDialog 多层模态窗口实现页面提交及刷新的代码
2009/11/28 Javascript
javascript call方法使用说明
2010/01/11 Javascript
Firefox中autocomplete=&quot;off&quot; 设置不起作用Bug的解决方法
2011/03/25 Javascript
file控件选择上传文件确定后触发的js事件是哪个
2014/03/17 Javascript
Node.js实现数据推送
2016/04/14 Javascript
多功能jQuery树插件zTree实现权限列表简单实例
2016/07/12 Javascript
JavaScript实现图片拖曳效果
2017/09/08 Javascript
[02:36]DOTA2混沌骑士 英雄基础教程
2013/11/26 DOTA
Python生成器(Generator)详解
2015/04/13 Python
python自动zip压缩目录的方法
2015/06/28 Python
python实现list由于numpy array的转换
2018/04/04 Python
django配置连接数据库及原生sql语句的使用方法
2019/03/03 Python
Python除法之传统除法、Floor除法及真除法实例详解
2019/05/23 Python
python把转列表为集合的方法
2019/06/28 Python
python logging添加filter教程
2019/12/24 Python
Python3 集合set入门基础
2020/02/10 Python
Python实现栈的方法详解【基于数组和单链表两种方法】
2020/02/22 Python
Python基于traceback模块获取异常信息
2020/07/23 Python
诺心蛋糕官网:LE CAKE
2018/08/25 全球购物
介绍一下linux的文件权限
2012/02/15 面试题
《神奇的克隆》教学反思
2014/04/10 职场文书
学习党的群众路线教育实践活动心得体会范文
2014/11/03 职场文书
2014年人力资源部工作总结
2014/11/19 职场文书
2014年初三班主任工作总结
2014/12/05 职场文书
教师学期个人总结
2015/02/11 职场文书
财务人员入职担保书
2015/09/22 职场文书
2016年度继续教育学习心得体会
2016/01/19 职场文书
推荐六本经典文学奖书籍:此生必读
2019/08/22 职场文书