巧用 :checked 实现跨层级折叠展开效果

512 阅读1分钟

image.png

在列表中如何实现这样的展开收起效果,常规方案是每条数据维护一个展开收起的状态,点击的时候改变这个状态。其实用伪类 :checked 也可以实现相同的效果,需要注意的是上图的 HTML 结构相对复杂一些,触发区(点击处)和内容区(展开收起的内容)不在同一层级,甚至不在同一父元素下,那么 <input type="checkbox" id="xxx" /><label for="xxx">展开</label> 就不能按常规那样放在一块(其实也可以,用父元素选择器 :has 能实现, 但目前 :has 兼容性要求还比较高),<input type="checkbox" id="xxx" /> 需要放到能通过 CSS 选择器同时覆盖触发区和内容区的层级。

<div v-for="item in taskList" :key="item.id">
  <input type="checkbox" :id="'result-' + item.id" class="task-checkbox" />
  <div class="task-item">
    <span class="task-item_name">{{ item.name }}</span>
    <span class="task-item_action">
      <el-tag :type="TaskAction[item.action].type" size="small">{{
        TaskAction[item.action].label
      }}</el-tag>
    </span>
    <span class="task-item_expand">
      <label :for="'result-' + item.id" class="result-expand"
        >展开审批意见 <FeIcon name="xia"
      /></label>
      <label :for="'result-' + item.id" class="result-collapse"
        >收起审批意见 <FeIcon name="shang"
      /></label>
    </span>
  </div>
  <div class="task-result">{{ item.result }}</div>
</div>
.task-item {
  display: flex;
  align-items: center;
  &_name {
    flex: 1;
  }
  &_action {
    flex: 1;
  }
  &_expand {
    flex-shrink: 0;
    label {
      user-select: none;
      cursor: pointer;
    }
    .result-collapse {
      display: none;
    }
  }
}
.task-result {
  display: none;
}
.task-checkbox {
  display: none;
  &:checked ~ .task-item {
    .result-expand {
      display: none;
    }
    .result-collapse {
      display: inline-block;
    }
  }
  &:checked ~ .task-result {
    display: block;
  }
}

使用 :has 会更优雅一点,:has 提供了一种针对引用元素选择父元素或者先前的兄弟元素的方法。这样 <input type="checkbox" id="xxx" /> 就不用放到外层了。

<div v-for="item in taskList" :key="item.id">
  <div class="task-item">
    <span class="task-item_name">{{ item.name }}</span>
    <span class="task-item_action">{{ item.action }}</span>
    <span class="task-item_expand">
      <input
        type="checkbox"
        :id="'result-' + item.id"
        class="task-checkbox"
      />
      <label :for="'result-' + item.id" class="result-expand"
        >展开审批意见 <FeIcon name="xia"
      /></label>
      <label :for="'result-' + item.id" class="result-collapse"
        >收起审批意见 <FeIcon name="shang"
      /></label>
    </span>
  </div>
  <div class="task-result">{{ item.result }}</div>
</div>
.task-item {
  display: flex;
  align-items: center;
  &_name {
    flex: 1;
  }
  &_action {
    flex: 1;
  }
  &_expand {
    flex-shrink: 0;
    .task-checkbox {
      display: none;
    }
    label {
      user-select: none;
      cursor: pointer;
    }
    .result-collapse {
      display: none;
    }
  }
  &:has(.task-checkbox:checked) {
    .result-expand {
      display: none;
    }
    .result-collapse {
      display: inline-block;
    }
    & + .task-result {
      display: block;
    }
  }
}
.task-result {
  display: none;
}