SunXinFei / sunxinfei.github.io

前后端技术相关笔记,已迁移到 Issues 中
https://github.com/SunXinFei/sunxinfei.github.io/issues
32 stars 3 forks source link

实现拖拽网格布局排版思路 #10

Open SunXinFei opened 5 years ago

SunXinFei commented 5 years ago

卡片拖拽顺序控制

先说下trello这种纵列卡片的拖拽布局,很简单,只需要卡片的相对位置顺序,加上css布局即可。 一般情况下,拖拽卡片改变卡片位置有一种泛泛的方式,那就是把最新的index全部交给后台保存,但是弊端很明显,影响数据库字段很多,在trello中,有一套单独的逻辑,来优化这方面的操作:

  1. 一套逻辑来计算拖拽的卡片的Pos位置字段,也就意味着后台数据库只改变拖拽的卡片Pos字段;
    • 当释放卡片位置为Top时,则为最小Pos/2;
    • 当释放卡片位置为两卡片之间时,则为(Pos1+Pos2)/2;
    • 当释放卡片位置为Bottom时,则为最大Pos+间距(如2048);
  2. 当触发极限小数情况,重新计算所有卡片Pos字段时,将会尽可能少的改变该纵列的Pos;
    • 当卡片位置小于0.125时触发重排Pos逻辑,即第一个为16384,后面依次类推;
    • 当重排Pos字段时,遵守能用则用的原则,即如果旧的Pos字段满足大于前面的重排后字段则不再改变; eg:0.125-0.25-16-32-64-589823-655359 在重排后为 16384-32768-49152-65536-327679.5-589823-655359
SunXinFei commented 5 years ago

卡片的标签与看板标签列表

根据trello业务和数据接口,整理逻辑如下:

  1. 同一个看板下的所有卡片共享当前看板的标签列表;
  2. 一个卡片上可以打N个不同标签,也可以取消打上的标签;
  3. 同一个看板的任何地方都可以获取标签列表,并进行增删改; 所以根据该业务逻辑,将看板的标签列表放入store中,方便进行增、删、改、查。 我们监听strore中标签列表的变化,如果产生了变化,通过前后新旧数据的数据结构对比,我们可以比较出标签列表的增、删、改,这三种状态,然后对应将卡片中的打上的标签,相应的删除和修改。 这里值得说一句就是,标签列表中的增删改会紧接着发送后台请求,将后台表关联关系解除,所以监听标签列表变化之后,对应卡片的标签的删改只是前端操作,无需任何后台请求。

    /**
    * 检测新旧数据的增删改
    * @param {Array} oldArr
    * @param {Array} newArr
    * @returns {Object} {flag: String(eg:'same','delete','change','add'),
    changeItem:Object(修改后的元素),
    deleteItem:Object(被删除的元素)}
    */
    export const checkAddDeleteChange = (oldArr, newArr) => {
    let resultObj = {
    flag: 'same'
    }
    if (oldArr.length === newArr.length) {//change
    //外层循环最新的数组,将修改后的最新结果,查找出来
    for (let j in newArr) {
      let flag = false
      for (let i in oldArr) {
        if (isObjectValueEqual(oldArr[i],newArr[j])) {
          flag = true;
          break;
        }
      }
      if (!flag) {
        resultObj.flag = 'change';
        resultObj.changeItem = newArr[j];
        break;
      }
    }
    } else if (oldArr.length > newArr.length) {//delete
    resultObj.flag = 'delete'
    //外层循环旧数组,找出被删除的元素
    for (let i in oldArr) {
      let flag = false
      for (let j in newArr) {
        if (oldArr[j] === newArr[i]) {
          flag = true;
          break;
        }
      }
      if (!flag) {
        resultObj.deleteItem = oldArr[i];
        break;
      }
    }
    } else if (oldArr.length < newArr.length) {//add
    resultObj.flag = 'add'
    }
    return resultObj;
    }
    /**
    * 检测两个对象是否相同
    * @param {Object} a
    * @param {Object} b
    * @returns {Boolean}
    */
    export const isObjectValueEqual = (a, b) => {
    // Of course, we can do it use for in 
    // Create arrays of property names
    var aProps = Object.getOwnPropertyNames(a);
    var bProps = Object.getOwnPropertyNames(b);
    
    // If number of properties is different,
    // objects are not equivalent
    if (aProps.length != bProps.length) {
    return false;
    }
    
    for (var i = 0; i < aProps.length; i++) {
    var propName = aProps[i];
    
    // If values of same property are not equal,
    // objects are not equivalent
    if (a[propName] !== b[propName]) {
      return false;
    }
    }
    
    // If we made it this far, objects
    // are considered equivalent
    return true;
    }
SunXinFei commented 5 years ago

瀑布流排版的简单思路

写瀑布流主要是跟后面的网格布局做对比,思路如下:

  1. 首先瀑布流排版要满足每一列的宽度要相同,每一列中的卡片可以高度参差不一。
  2. 我们给容器一个宽度,可以容纳N个宽度相同的卡片,在渲染下一个卡片的时候,需要计算当前页面中最短一列,然后将N+1的这个卡片塞进去,然后再计算当前页面中最短一列,塞入第N+2个卡片,依次类推。
  3. 监听浏览器的scroll滚动条事件,每次加载新的一个卡片,就需要计算当前页面中的最短列,然后在该列渲染新的卡片。获取页面最短列的计算方法页非常简单,具体算法会在后面的网格布局中提到。
SunXinFei commented 5 years ago

网格布局的思路

  1. 网格布局在目前的浏览器支持中,纯Css是不能搞定网格布局的,感兴趣的可以去看下新的css标准grid-layout,纯css实现网格布局。用目前的float或者flex都不能完全满足网格布局的要求,所以目前网格布局只能通过js控制css的位置来进行定位。
  2. 网格布局有一个容器为container,里面有若干大小不一的card,拖拽card时,我们在container上对card进行排版,所以当拖拽card与目标card重叠时,我们将目标card进行重排,所以这里说明一下,拖拽card与目标card的重叠等判断事件,应该写在container身上,而不是写在card身上加hover事件,因为网格布局会有一种情况:container中出现容纳若干card的空隙,需要拖拽card可以放置在空隙位置,这样情况下,card上绑定hover等事件就不能满足需求。