44

动态分栏布局实现

 5 years ago
source link: http://yalishizhude.github.io/2018/06/12/ls/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近项目上要在浏览器端实现一个类似 Linux 下 ls 命令的显示效果。虽然实现过程没花多长时间,但是觉得这个效果还挺赞的,所以分享一下。

神奇的ls命令

ls命令对于前端同学可能比较陌生,简单来说就是Linux终端中的一个常用功能,用来显示某个目录下的文件(夹)列表。不过这它的功能不是本文讨论的重点。重点是它神奇的显示效果,比如下面这样:

yEVzmmb.jpg!web

看似并无神奇之处,就是文件(夹)名称逐行显示而已,别急,再来看下面两张截图:

6rEjyya.jpg!webMfEnEnm.jpg!web

不知道你现在有没有发现布局发生了变化:由最初的1列变成了多列!

你很容易想到列数量会随着窗口的宽度而改变,这种猜测是对的,但并不全面,因为决定其显示排列的还有文件(夹)名本身的长度。比如我再指定目录执行一条 ls 命令进行对比:

M3QzmmA.jpg!web

两条命令执行时窗口宽度是一样的,但是在长文件(夹)名的情况下是3列,而短文件(夹)下是4列。

ok~了解完 ls 命令的显示特点之后问题来了,我们该怎么实现呢?

实现难点与解决方案

更多文章请关注公众号“Web学习社”。

这种列数发生变化的情况在网站页面中并不少见,你可能第一个就会想到媒体查询或者Bootstrap。但很遗憾事情并没有这么简单。媒体查询和Bootstrap都是针对不同屏幕宽度进行的自适应,而并不会根据内容自动调整,无法满足我们的要求。

似乎浮动可以根据内容大小来进行自动排列,但和我们的需求相比也有一个很大的差别。那就是 ls 命令在调整好合适的列数之后,每列文件(夹)名都是等宽左对齐的。 而使用浮动后的效果是每行长的名称排列少,短的名称排列多,造成列数不统一,参差不齐。

到此看似毫无头绪,不过可以参考“把大象放进冰箱里”分几步的例子,分步骤来解决这个问题。这个问题我们其实可以分两步进行分析:

  1. 显示的列数需要根据窗口大小和名称长度两者共同决定,按照已知的使用css的方式无法解决。那么我们只能用最笨的方式进行动态计算。
  2. 列数确定以后要确保每列等宽等间距,且左对齐,这个比较容易实现,可以设置等宽等间距。

按照这个思路我们来实现第1步,那就是怎么计算出合适的列数。看看列数是由哪些因素决定的:

  • 窗口的宽度。
  • 名称的长度。
  • 名称之间的横向间距。

窗口的宽度可以通过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

QZviEbi.gif

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK