H5 implements fixed header and first column table

1. Development process

Tried during development

1. Use sticky layout and fixed sticky positioning and fixed positioning, but there are some problems, such as:

1.1. Sticky layout will fail in the browser that comes with Android phones (such as oppo and vivo), and the header will not be fixed.

1.2. During the sliding process, the table will have a dragging effect (it looks like it is out of the document flow, and there will be a blank space, but it will bounce back when you release it)

1.3. On the basis of 1.2, the fixed first column is positioned in its original place, making the page look separated into three parts.

2. Final plan

Divide the table into three parts: header, first column, and table body. The scrolling effect of the table body is achieved by overfloe:scroll, and the header and first column are achieved by dynamically setting the offset value of absolute positioning through the scrolling event of the table body.

2. Realization

1、HTML

  <div class="content-table" v-show="tabActive == 2">
        <!-- 表头 -->
        <div class="rowHeader">
          <table class="rowHeader-table" id="rowHeader-table">
            <tr>
              <td class="td seller_name tableH_fixed-left">销售员</td>
              <td class="td seller_phone">手机号</td>
              <td class="td one_branch">一级分校</td>
              <td class="td two_branch">二级分校</td>
              <td class="td total_uv double">累计访问人数</td>
              <td class="td total_time double">累计访问时长(min)</td>
              <td class="td add_uv double">昨日新增访问人数</td>
              <td class="td add_time double">昨日新增访问时长(min)</td>
            </tr>
          </table>
        </div>
        <!-- 列头 -->
        <div class="columnHeader">
          <table class="columnHeader-table" id="columnHeader-table">
            <tr v-for="(item, index) in sellersList" :key="index">
              <td class="td seller_name">
                {
   
   { item.sellerName }}
              </td>
            </tr>
          </table>
        </div>
        //表格滚动主体
        <div class="tableBody" id="tableBody" @scroll="scrollFn($event)">
          <table class="tableBody-table" id="tableBody-table">
            <tr v-for="(item, index) in sellersList" :key="index">
              <td class="td seller_phone">{
   
   { item.sellerPhone }}</td>
              <td class="td one_branch">{
   
   { item.branchName }}</td>
              <td class="td two_branch">{
   
   { item.secondaryBranchName }}</td>
              <td class="td total_uv">{
   
   { item.totalUv }}</td>
              <td class="td total_time">{
   
   { item.totalVisitTime.toFixed(2) }}</td>
              <td class="td add_uv">{
   
   { item.increasedUv }}</td>
              <td class="td add_time">{
   
   { item.increasedVisitTime.toFixed(2) }}</td>
            </tr>
          </table>
        </div>
      </div>
    </div>

2、css

.content-table {
  position: absolute;
  left: 16px;
  top: 234px;
  width: 343px;
  height: calc(100% - 268px);
  // 表头
  .rowHeader {
    position: relative;
    width: 343px;
    max-width: 343px;
    overflow: hidden;
    left: 0px;
    // 表头table
    .rowHeader-table {
      position: relative;
      width: fit-content;
      height: 52px;
      border-spacing: 0px;
      .tableH_fixed-left {
        position: sticky;
        left: 0;
      }
      td {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 52px;
        color: #fff;
        font-size: 14px;
        background: #5b8ff9;
        border-right: 1px solid #fff;
        padding: 0px;
      }
      .seller_name {
        border-radius: 16px 0 0 0;
      }
    }
  }

  // 列头
  .columnHeader {
    position: relative;
    max-height: calc(100% - 55px);

    overflow: hidden;
    width: 80px;
    top: 0px;

    .columnHeader-table {
      position: relative;
      border-spacing: 0px;
      tr {
        background-color: #fff;
        box-sizing: border-box;
        td {
          font-weight: 600;
          border: 1px solid #f7f7f7;
          border-top: none;
          border-right: none;
        }
        &:first-child {
          td {
            padding: 0px;
            box-sizing: border-box;
          }
        }
      }
    }
  }

  // 表格本体
  .tableBody {
    width: 265px;
    max-height: calc(100% - 55px);
    position: absolute;
    top: 52px;
    left: 80px;
    overflow: auto;
    .tableBody-table {
      border-spacing: 0px;
      tr {
        height: 35px;
        &:nth-child(odd) {
          background: #e8f7ff;
          // border: 0.02667rem solid #f7f7f7;
        }
        &:nth-child(even) {
          background: #fff9ed;
        }
        td {
          height: 100%;
          font-size: 12px;
          text-align: center;
          // font-weight: 500;
          padding: 0px;
          border: 1px solid #f7f7f7;
          border-top: none;
          border-right: none;
          box-sizing: border-box;
          &:last-child {
            border-right: 1px solid #f7f7f7;
          }
        }
      }
    }
  }

  // 表格元素
  tr {
    width: fit-content;
    white-space: normal;
    display: flex;
    .td {
      display: block;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 35px;
      color: #262626;
      text-align: center;
      box-sizing: border-box;
    }
    .seller_name {
      width: 80px;
      box-sizing: border-box;
    }
    .seller_phone,
    .one_branch,
    .two_branch {
      width: 84px;
      text-align: center;
      box-sizing: border-box;
    }
    .total_uv,
    .total_time,
    .add_uv {
      width: 64px;
      text-align: center;
      box-sizing: border-box;
    }
    .add_time {
      width: 71px;
      text-align: center;
      box-sizing: border-box;
    }
  }
}

3. js implements table header and first column scrolling

    scrollFn (event) {
      const col = document.getElementById('columnHeader-table')
      col.style.top = -event.target.scrollTop + 'px'
      const row = document.getElementById('rowHeader-table')
      row.style.left = -event.target.scrollLeft + 'px'
    },

4. Adjustments to bugs encountered

bug1: ios rubber band effect

Solution: Dealing with the rubber band effect of ios scrolling_Duoduo's little baby's blog-CSDN blog

Bug2: Vertical scrolling and horizontal scrolling in iOS are not independent (when sliding horizontally, it will scroll slightly vertically, and when sliding vertically, it will scroll slightly horizontally)

Solution: Disable iOS's own scrolling and use code to achieve the scrolling effect (but the scrolling will not be as smooth as browser scrolling. If you guys have a better solution, you can discuss it together)

  async mounted () {
    if (/(iPhone|iPad|iPod)/i.test(navigator.userAgent)) {
      console.log('是iOS设备')
      // 阻止默认的处理方式(阻止下拉滑动的效果)
      // passive 参数不能省略,用来兼容ios和android
      const tableBody = document.getElementById('tableBody')
      // 开始滑动的
      let startPos, endPos, isScrolling, scrollTime
      tableBody.addEventListener('touchstart', function (e) {
        var touch = e.targetTouches[0] // touches数组对象获得屏幕上所有的touch,取第一个touch
        // 开始滑动的时间戳
        scrollTime = new Date().getTime()
        startPos = { x: touch.pageX, y: touch.pageY, time: +new Date() } // 取第一个touch的坐标值
        if (tableBody.offsetHeight > tableBody.offsetWidth) {
          e.preventDefault()
        }
      }, { passive: false })
      // 触摸移动
      tableBody.addEventListener('touchmove', function (e) {
        var touch = e.targetTouches[0]
        endPos = { x: touch.pageX - startPos.x, y: touch.pageY - startPos.y }
        isScrolling = Math.abs(endPos.x) < Math.abs(endPos.y) ? 1 : 0 // isScrolling为1时,表示纵向滑动,0为横向滑动
        if (isScrolling == 0) { // 横向
          // 横向滚动纵向不变
          const time = (new Date().getTime() - scrollTime) / 1000
          const speed = Math.abs(endPos.x) / time
          // 判断左滑还是右滑,在左端就禁止左滑
          if (endPos.x > 0 && tableBody.scrollLeft == 0 && endPos.x < startPos.x) { // 右滑
            console.log('左滑')
            e.preventDefault()
          } else {
            console.log('endPos.x ', endPos.x)
            if (endPos.x < 0) {
              tableBody.scrollTo({ left: `${tableBody.scrollLeft + speed * 0.7}`, behavior: 'smooth' })
            } else {
              tableBody.scrollTo({ left: `${tableBody.scrollLeft - speed * 0.7}`, behavior: 'smooth' })
            }
            const row = document.getElementById('rowHeader-table') // 表头
            row.style.left = -tableBody.scrollLeft + 'px'
          }
        }
        if (isScrolling == 1) { // 纵向
          const time = (new Date().getTime() - scrollTime) / 1000
          const speed = Math.abs(endPos.y) / time
          console.log('speed', speed)
          // console.log('hh_endPos.y', endPos.y) // 上滑<0,下滑》0
          if (tableBody.scrollTop == 0 && endPos.y > 0) {
            e.preventDefault()
          } else {
            // 纵向滚动横向不变
            if (endPos.y < 0) {
              tableBody.scrollTo({ top: `${tableBody.scrollTop + speed * 0.8}`, behavior: 'smooth' })
            } else {
              tableBody.scrollTo({ top: `${tableBody.scrollTop - speed * 0.8}`, behavior: 'smooth' })
            }
            const col = document.getElementById('columnHeader-table')// 首列
            col.style.top = -tableBody.scrollTop + 'px'
          }
        }
      }, { passive: false })
    }
  },

Guess you like

Origin blog.csdn.net/weixin_45371730/article/details/132720062