js div span等节点添加focus聚焦、blur失焦事件(tabindex属性)

场景:下拉弹框显示时,想要点击其他地方即隐藏(不使用蒙板,下拉弹框定位到点击显示的位置)

tabindex可以使得相应的节点具有 focus 和 blur 事件

tabindex=负值
(通常是tabindex='-1'),表示元素是可聚焦的,但是不能通过键盘导航来访问到该元素,用JS做页面小组件内部键盘导航的时候非常有用。
tabindex='0',
表示元素是可聚焦的,并且可以通过键盘导航来聚焦到该元素,它的相对顺序是当前处于的DOM结构来决定的。
tabindex=正值,
表示元素是可聚焦的,并且可以通过键盘导航来访问到该元素;它的相对顺序按照tabindex 的数值递增而滞后获焦。如果多个元素拥有相同的 tabindex,它们的相对顺序按照他们在当前DOM中的先后顺序决定。
注:tabindex 的最大值不应超过 32767。如果没有指定,它的默认值为 -1。

tabindex == -1 时无法通过tab键选中该节点
tabindex == 0 或 1 都可以通过tab键选中该节点(不同的赋值表示不同的优先级)

template

<template>
  <div class="dropDownBox">
    <div class="drop-button" tabindex="0" @click="clickFunc" @blur="blurFunc">
      <div> {{ data[activeIndex].text }} </div>
      <div class="icon" :class="isShowBox ? 'rotate' : ''"> </div>

      <div class="box" v-show="isShowBox">
        <div 
          class="item" 
          :class="activeIndex === index ? 'select-color' : ''"
          v-for="(item, index) in data"
          :key="index"
          @click="changeOption(index)"
          >
          {{ item.text }}
        </div>
      </div>
    </div>
  </div>
</template>

script方法

export default {
  name: 'DropDownBox',
  data() {
    return {
      // 下拉框选项数据
      data: [
        {
          text: 'DOW J',
        },
        {
          text: 'S/P 500',
        },
        {
          text: 'NASDAQ',
        },
      ],
      // 是否显示下拉框
      isShowBox: false,
      // 当前选项
      activeIndex: 0,
    };
  },
  methods: {
    clickFunc() {
      this.isShowBox = !this.isShowBox;
    },
    blurFunc() {
      this.isShowBox = false;
    },
    changeOption(index) {
      if (this.activeIndex === index) {
        return;
      }
      this.activeIndex = index;
    }
  }
}
</script>

style

<style lang="less" scoped>
  .dropDownBox {
    .drop-button {
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      outline: none;

      .rotate {
        transform: rotate(180deg);
      }

      .icon {
         0;
        height: 0;
        border-left: 6px solid transparent;
        border-right: 6px solid transparent;
        border-top: 6px solid #000;
      }
      .drop-down-box-bg {
        background-color: #e6e6e6;
      }
      .select-color {
        color: #fb7299,
      }

      .box {
        position: absolute;
        top: 20px;
        z-index: 11;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        padding: 20px;
        background-color: #fff;
        box-shadow: 0px 3px 29px 0px rgba(59,74,116,0.14);
        border-radius: 8px;
        .item {
          font-size: 16px;
          line-height: 16px;
          font-weight: 500;
          padding: 6px 0;
        }
      }
    }
  }
</style>

完整代码

<template>
  <div class="dropDownBox">
    <div class="drop-button" tabindex="0" @click="clickFunc" @blur="blurFunc">
      <div> {{ data[activeIndex].text }} </div>
      <div class="icon" :class="isShowBox ? 'rotate' : ''"> </div>

      <div class="box" v-show="isShowBox">
        <div 
          class="item" 
          :class="activeIndex === index ? 'select-color' : ''"
          v-for="(item, index) in data"
          :key="index"
          @click="changeOption(index)"
          >
          {{ item.text }}
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'DropDownBox',
  data() {
    return {
      // 下拉框选项数据
      data: [
        {
          text: 'DOW J',
        },
        {
          text: 'S/P 500',
        },
        {
          text: 'NASDAQ',
        },
      ],
      // 是否显示下拉框
      isShowBox: false,
      // 当前选项
      activeIndex: 0,
    };
  },
  methods: {
    clickFunc() {
      this.isShowBox = !this.isShowBox;
    },
    blurFunc() {
      this.isShowBox = false;
    },
    changeOption(index) {
      if (this.activeIndex === index) {
        return;
      }
      this.activeIndex = index;
    }
  }
}
</script>

<style lang="less" scoped>
  .dropDownBox {
    .drop-button {
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      outline: none;

      .rotate {
        transform: rotate(180deg);
      }

      .icon {
         0;
        height: 0;
        border-left: 6px solid transparent;
        border-right: 6px solid transparent;
        border-top: 6px solid #000;
      }
      .drop-down-box-bg {
        background-color: #e6e6e6;
      }
      .select-color {
        color: #fb7299,
      }

      .box {
        position: absolute;
        top: 20px;
        z-index: 11;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        padding: 20px;
        background-color: #fff;
        box-shadow: 0px 3px 29px 0px rgba(59,74,116,0.14);
        border-radius: 8px;
        .item {
          font-size: 16px;
          line-height: 16px;
          font-weight: 500;
          padding: 6px 0;
        }
      }
    }
  }
</style>

下拉框滚动遮挡问题

问题: 因为需要下拉框显示时阻止滚动,这导致显示时可能被遮挡
解决方法: 先滚到到完整显示,再使用overflow阻止滚动,隐藏时再移除overflow

完整代码:

<template>
  <div class="dropDownBoxCover">
    <div class="drop-button" tabindex="0" @click="clickFunc" @blur="blurFunc">
      <div> {{ data[activeIndex].text }} </div>
      <div class="icon" :class="isShowBox ? 'rotate' : ''"> </div>

      <div ref="box" class="box" v-show="isShowBox">
        <div 
          class="item" 
          :class="activeIndex === index ? 'select-color' : ''"
          v-for="(item, index) in data"
          :key="index"
          @click="changeOption(index)"
          >
          {{ item.text }}
        </div>
      </div>
    </div>
    <!-- 蒙板 -->
    <div class="cover" v-show="isShowBox"></div>
  </div>
</template>

<script>
export default {
  name: 'DropDownBoxCover',
  data() {
    return {
      // 下拉框选项数据
      data: [
        {
          text: 'DOW J',
        },
        {
          text: 'S/P 500',
        },
        {
          text: 'NASDAQ',
        },
      ],
      // 是否显示下拉框
      isShowBox: false,
      // 当前选项
      activeIndex: 0,
    };
  },
  methods: {
    // 点击显示下拉框
    clickFunc() {
      this.isShowBox = !this.isShowBox;

      if (this.isShowBox) {
        this.$nextTick(() => {
          // 否则拿不到 $refs.box
          this.showFullBox();
        })
        this.switchScroll(false);
      } else {
        this.switchScroll(true);
      }
    },
    // blur失焦
    blurFunc() {
      this.isShowBox = false;

      this.switchScroll(true);
    },
    // 切换选项
    changeOption(index) {
      if (this.activeIndex === index) {
        return;
      }
      this.activeIndex = index;
    },
    // 是否开放滚动
    switchScroll(isScroll) {
      if (isScroll) {
        document.body.style.overflow = '';
      } else {
        document.body.style.overflow = 'hidden';
      }
    },
    // 计算滚动距离并滚动显示完整
    showFullBox() {
      // 按钮距离底部距离
      const toBottom = document.documentElement.clientHeight - this.$refs.box.getBoundingClientRect().bottom;
      console.log(1, document.documentElement.clientHeight);
      console.log(2, this.$refs.box.getBoundingClientRect())

      if (toBottom < 0) {
        window.console.log('box 被遮挡了')
        const {scrollTop} = document.documentElement;
        // 需要滚动的距离(被遮挡的高度)
        const distance = -toBottom;
        document.documentElement.scrollTop = scrollTop + distance;
      }
    }
  }
}
</script>

<style lang="less" scoped>
  .dropDownBoxCover {
    .drop-button {
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      outline: none;

      .rotate {
        transform: rotate(180deg);
      }

      .icon {
         0;
        height: 0;
        border-left: 6px solid transparent;
        border-right: 6px solid transparent;
        border-top: 6px solid #000;
      }
      .drop-down-box-bg {
        background-color: #e6e6e6;
      }
      .select-color {
        color: #fb7299,
      }

      .box {
        position: absolute;
        top: 20px;
        z-index: 11;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        padding: 20px;
        background-color: #fff;
        box-shadow: 0px 3px 29px 0px rgba(59,74,116,0.14);
        border-radius: 8px;
        .item {
          font-size: 16px;
          line-height: 16px;
          font-weight: 500;
          padding: 6px 0;
        }
      }
    }

    .cover {
      position: fixed;
      top: 0;
      left: 0;
       100%;
      height: 100%;
      z-index: 10;
      background-color: rgba(0,0,0,0.3);
    }
  }
</style>
原文地址:https://www.cnblogs.com/nangezi/p/14872730.html