为了账号安全,请及时绑定邮箱和手机立即绑定

动态分栏布局实现

2018.06.12 23:25 950浏览
字号
大字 中字 小字
最近项目上要在浏览器端实现一个类似 Linux 下 ls 命令的显示效果。虽然实现过程没花多长时间,但是觉得这个效果还挺赞的,所以分享一下。 <!-- more -->
神奇的ls命令 ls命令对于前端同学可能比较陌生,简单来说就是Linux终端中的一个常用功能,用来显示某个目录下的文件(夹)列表。不过这它的功能不是本文讨论的重点。重点是它神奇的显示效果,比如下面这样: {% 看似并无神奇之处,就是文件(夹)名称逐行显示而已,别急,再来看下面两张截图: {% {% 不知道你现在有没有发现布局发生了变化:由最初的1列变成了多列!
你很容易想到列数量会随着窗口的宽度而改变,这种猜测是对的,但并不全面,因为决定其显示排列的还有文件(夹)名本身的长度。比如我再指定目录执行一条 ls 命令进行对比:
{% 两条命令执行时窗口宽度是一样的,但是在长文件(夹)名的情况下是3列,而短文件(夹)下是4列。 ok~了解完ls命令的显示特点之后问题来了,我们该怎么实现呢?
实现难点与解决方案 更多文章请关注公众号“Web学习社”。 这种列数发生变化的情况在网站页面中并不少见,你可能第一个就会想到媒体查询或者Bootstrap。但很遗憾事情并没有这么简单。媒体查询和Bootstrap都是针对不同屏幕宽度进行的自适应,而并不会根据内容自动调整,无法满足我们的要求。 似乎浮动可以根据内容大小来进行自动排列,但和我们的需求相比也有一个很大的差别。那就是ls命令在调整好合适的列数之后,每列文件(夹)名都是等宽左对齐的。而使用浮动后的效果是每行长的名称排列少,短的名称排列多,造成列数不统一,参差不齐。 到此看似毫无头绪,不过可以参考“把大象放进冰箱里”分几步的例子,分步骤来解决这个问题。这个问题我们其实可以分两步进行分析:
  • 显示的列数需要根据窗口大小和名称长度两者共同决定,按照已知的使用css的方式无法解决。那么我们只能用最笨的方式进行动态计算。
  • 列数确定以后要确保每列等宽等间距,且左对齐,这个比较容易实现,可以设置等宽等间距。
  • 按照这个思路我们来实现第1步,那就是怎么计算出合适的列数。看看列数是由哪些因素决定的:
    1. 窗口的宽度。
    2. 名称的长度。
    3. 名称之间的横向间距。
    窗口的宽度可以通过dom获取或者样式设定,名称宽度可以通过fontSize进行计算,一般一个中文字符宽度为一个fontSize值,而英文数字则为一半,这里我们为了方便计算,只考虑英文和数字的情况。横向间距则可以由样式设定。那么可以得出计算公式: 窗口宽度 >= 列数 * (名称长度 * fontSize/2 + 间距) 这个不等式并不准确,因为当列数等于1的时候是最有可能满足这个不等式的,但显然不是我们想要的结果,所以还要加上另一个不等式: 窗口宽度 < (列数 + 1) * (名称长度 * fontSize/2 + 间距) 满足这两个条件的列数才是我们的最优解。不等式右边的表达式实际上就是行宽,更准确的说是每一行的行宽。当然这个表达式其实也不严谨,更严谨的是下面这样: 窗口宽度 >= (名称长度1 * fontSize/2 + 间距) + ... + (名称长度n * fontSize/2 + 间距) 窗口宽度 < (名称长度1 * fontSize/2 + 间距) + ... + (名称长度n+1 * fontSize/2 + 间距) 代码如下: / ** * 根据列数和名称长度进行分列,以二维数组的方式返回结果,如果超出宽度则返回空数组 * list {Array} 待分列的名称数组 * colnum {Number} 列数 * size {Number} 字体大小 * width {Number} 用父容器的宽度替代前面所说的窗口宽度 * spaceWidth {Number} 间距 */ var subfield = function(list, colnum, size, width, spaceWidth) { // 使用lodash的chunk函数将数组按照列数分割成二维数组 var result = _.chunk(list, Math.min(colnum, list.length)) var rowWidth = 0 // 逐列计算宽度 for(var col=0;col<result[0].length;col++) { colWidth = 0 for(var row=0; row<result.length; row++){ var item = result[row][col] || '' // 每列宽度取决于最长的名称宽度 colWidth = Math.max(colWidth, item.length * size / 2 + spaceWidth) } rowWidth += colWidth } return rowWidth < width ? result : [] } 有了上面的不等式,那么现在我们就可以开始逐行计算了,不过我们从分几列开始算起?如果从分1列的方式算起,那么大多数情况下前面的分列方式都不是最优解,所以我们考虑直接从最优解算起,找到最长的名称和最短的名称并假设它们在一列,如果不满足则减少一列。 // 先取最理想的列数 var colnum = Math.max(1, Math.floor((containerWidth - maxFile.length * fontSize / 2 - spaceWidth) / (spaceWidth + fontSize / 2 * minFile.length))) var result = [] // 按照递减方式直到找到最优解 while (colnum > 0 && result.length===0) { if (colnum === 1) { result = [] for(var i=files.length;i>0;i--) { result.push([files.shift()]) } } else { result = subfield(files, colnum, fontSize, containerWidth, spaceWidth) } colnum-- } 至此,我们就完成了第1步。第2步这种布局方式其实很常见,其实就是古老的table布局,那么我们渲染到页面的时候直接使用table来实现。 var render = function(list) { var str = '' list.forEach(function(row) { str += '<tr><td>'+ row.join('</td><td>') + '</td></tr>' }) document.querySelector('.ls table').innerHTML = str } 具体代码: http://jsbin.com/zipabig/1/edit?html,css,js,output {%
    本文原创发布于慕课网 ,转载请注明出处,谢谢合作
    9人推荐
    若觉得本文不错,就分享一下吧!
    看过此文的用户,还看了以下文章
    正在加载中
    1. 评论
    2. 收藏
    3. 共同学习,写下你的评论
    意见反馈 常见问题 APP下载
    官方微信
    举报
    0/150
    提交
    取消
    hv128