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 相关文章推荐
CSS常用网站布局实例
Apr 03 Javascript
jQuery如何防止这种冒泡事件发生
Feb 27 Javascript
jquery判断密码强度的验证代码
Apr 22 Javascript
JavaScript中有关一个数组中最大值和最小值及它们的下表的输出的解决办法
Jul 01 Javascript
微信小程序 火车票查询实例讲解
Oct 17 Javascript
Angular 4.x 路由快速入门学习
May 03 Javascript
layui表格checkbox选择全选样式及功能的实例
Mar 07 Javascript
vue-cli整合vuex的时候,修改actions和mutations,实现热部署的方法
Sep 19 Javascript
js验证身份证号码记录的方法
Apr 26 Javascript
JavaScript展开操作符(Spread operator)详解
Jul 20 Javascript
Node.js API详解之 querystring用法实例分析
Apr 29 Javascript
JS+canvas五子棋人机对战实现步骤详解
Jun 04 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网页显示各种语法错误
2013/09/23 PHP
ubuntu下配置nginx+php+mysql详解
2015/09/10 PHP
调试WordPress中定时任务的相关PHP脚本示例
2015/12/10 PHP
javascript 火狐(firefox)不显示本地图片问题解决
2008/07/05 Javascript
JavaScript使用技巧精萃[代码非常实用]
2008/11/21 Javascript
prettify 代码高亮着色器google出品
2010/12/28 Javascript
javascript中的throttle和debounce浅析
2014/06/06 Javascript
JavaScript实现自动生成网页元素功能(按钮、文本等)
2015/11/21 Javascript
jQuery遍历DOM的父级元素、子级元素和同级元素的方法总结
2016/07/07 Javascript
AngularJs Injecting Services Into Controllers详解
2016/09/02 Javascript
react-native DatePicker日期选择组件的实现代码
2017/09/12 Javascript
微信小程序--特定区域滚动到顶部时固定的方法
2019/04/28 Javascript
Vue 幸运大转盘实现思路详解
2019/05/06 Javascript
微信小程序实现轨迹回放的示例代码
2019/12/13 Javascript
在微信小程序中渲染HTML内容3种解决方案及分析与问题解决
2020/01/12 Javascript
vue-cli4项目开启eslint保存时自动格式问题
2020/07/13 Javascript
python 简单的绘图工具turtle使用详解
2017/06/21 Python
Python基于正则表达式实现文件内容替换的方法
2017/08/30 Python
用tensorflow搭建CNN的方法
2018/03/05 Python
python实现顺序表的简单代码
2018/09/28 Python
python 实现调用子文件下的模块方法
2018/12/07 Python
对Python中小整数对象池和大整数对象池的使用详解
2019/07/09 Python
Python requests模块session代码实例
2020/04/14 Python
序列化Python对象的方法
2020/08/01 Python
HTML5 语音搜索(淘宝店语音搜素)
2013/01/03 HTML / CSS
十八大感想感言
2014/02/10 职场文书
2014年大学班长工作总结
2014/11/14 职场文书
2015教师见习期工作总结
2014/12/12 职场文书
文明旅游倡议书
2015/04/28 职场文书
拾金不昧表扬信怎么写
2015/05/04 职场文书
2015年七夕情人节感言
2015/08/03 职场文书
关于做家务的心得体会
2016/01/23 职场文书
详细谈谈MYSQL中的COLLATE是什么
2021/06/11 MySQL
Java数组与堆栈相关知识总结
2021/06/29 Java/Android
一篇文章看懂MySQL主从复制与读写分离
2021/11/07 MySQL
解决 Redis 秒杀超卖场景的高并发
2022/04/12 Redis