自定義select彈出面板

自定義select彈出面板:

根據按下的按鍵和option的內容定位到option、選中項左側標記、自定義滾動條、上下鍵滾動列表,回車選定、事件委託

效果圖:

//js調用:
initSelect(s); //s爲select的id或select的dom對象

selectDemo.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>select</title>
  <style>
    body {
      background: #999999;
    }

    #info {
      height: 400px;
      margin-bottom: 50px;
      overflow-y: scroll;
    }

    .baseSelect {
      height: 30px;
      width: 120px;
      background-color: #252833;
      color: rgba(255, 255, 255, 0.692);
      font-size: 16px;
      border-radius: 3px;
      border: none;
      outline: none;
    }

    .selectPosition {
      position: relative;
      height: 0;
    }

    .selectPopupPanel {
      position: absolute;
      border-radius: 2px;
      box-shadow: 1px 2px 10px 0 rgba(0, 0, 0, 0.5);
      border: solid 1px rgba(213, 213, 213, 0.13);
      background-color: #252833;
      cursor: pointer;
      outline: none;
      display: none;
      overflow: hidden;
      opacity: 0;
      transition: opacity .4s;
    }

    .selectPopupPanel .scrollBar {
      position: absolute;
      top: 0;
      right: 0;
      width: 4px;
      background-color: transparent;
    }

    .selectPopupPanel .scrollBar .slidBlock {
      position: absolute;
      width: 100%;
      border-radius: 2px;
      background-color: #3d4351;
    }

    .selectPopupPanel .scrollBar .slidBlock:hover {
      box-shadow: -1px 0 3px rgba(241, 243, 243, 0.5);
    }

    .selectPopupPanel .selectOptions {
      overflow-x: hidden;
      overflow-y: scroll;
      background-color: #252833;
      margin-right: -50px;
    }

    .selectPopupPanel .selectOptions .option {
      width: 100%;
      height: 42px;
      background-color: transparent;
      position: relative;
    }

    .selectPopupPanel .selectOptions .option .tag {
      position: absolute;
      width: 4px;
      height: 100%;
    }

    .selectPopupPanel .selectOptions .optionSelected {
      background-color: #343743;
    }

    .selectPopupPanel .selectOptions .optionSelected .tag {
      background-image: linear-gradient(5deg, #ff5858 15%, #f857a6 80%);
    }

    .selectPopupPanel .selectOptions .optionFoucs {
      background-color: #343743;
    }

    .selectPopupPanel .selectOptions .option .optionText {
      position: absolute;
      left: 4px;
      right: 40px;
      overflow:hidden;
      text-overflow:ellipsis;
      white-space:nowrap;
      line-height: 42px;
      padding-left: 8px;
      font-family: Roboto;
      font-size: 16px;
      font-weight: normal;
      font-stretch: normal;
      font-style: normal;
      letter-spacing: normal;
      color: #b1b8c8;
      user-select: none;
    }
  </style>
</head>

<body>
<div id="info">info:</div>
<div>
  <input type="checkbox" id="c1" checked="checked">
  <label for="c1">Use custom</label>
</div>
<div>item:</div>
<select class="baseSelect" id="s1">
  <option value="January">January</option>
  <option value="February">February</option>
  <option value="March">March</option>
  <option value="April">April</option>
  <option value="May">May</option>
  <option value="June">June</option>
  <option value="July">July</option>
  <option value="August">August</option>
  <option value="September">September</option>
  <option value="October">October</option>
  <option value="November">November</option>
  <option value="December">December</option>
  <option value="January2">January2</option>
  <option value="February2">February2</option>
  <option value="March2">March2</option>
  <option value="April2">April2</option>
  <option value="May2">May2</option>
  <option value="June2">June2</option>
  <option value="July2">July2</option>
  <option value="August2">August2</option>
  <option value="September2">September2</option>
  <option value="October2">October2</option>
  <option value="November2">November2</option>
  <option value="December2">December2</option>
</select>
<div style="margin-top: 600px;">footer</div>

<script>
  function initSelect(value) {
    let select = null;
    if (typeof value == "string") {
      select = document.getElementById(value);
    } else {
      select = value;
    }

    createPopupPanel();

    let selectPopupPanel = select.parentNode.getElementsByClassName("selectPopupPanel")[0];

    let selectCtrl = null;
    let timeoutId = null;
    let bodyOldOverflowY = null;

    select.handles = addSelectListeners();

    function addSelectListeners() {
      let handles = {
        "mousedown": function () {
          this.mousedownTime = new Date().getTime();
          this.mouseup = false;
        },
        "click": function () {
          if (new Date().getTime() - this.mousedownTime < 500) {
            timeoutId = showSelectPanel();
          }
        },
        "keyup": function (e) {
          e = e || arguments.callee.caller.arguments[0];
          if (!e) {
            return;
          }
          let keyCode = e.keyCode;
          if (keyCode == 38 || keyCode == 40 || keyCode == 13) {
            showSelectPanel();
          }
        }
      };

      select.addEventListener("mousedown", handles["mousedown"]);
      select.addEventListener("click", handles["click"]);
      select.addEventListener("keyup", handles["keyup"]);

      return handles;
    }

    function showSelectPanel() {
      if (selectPopupPanel.style.display == "block") {
        return;
      }
      if (!selectCtrl) {
        selectCtrl = initSelectList(select, selectPopupPanel);
      }
      return setTimeout(function () {
        showInfo("show");
        timeoutId = null;
        selectPopupPanel.style.display = "block";
        selectCtrl.calcRect();
        selectCtrl.move(select.selectedIndex, true, true);
        selectPopupPanel.style.opacity = 1;
        bodyOldOverflowY = document.body.style.overflowY;
        document.body.style.overflowY = "hidden";
        selectPopupPanel.focus();
      }, 50);
    }

    function hideSelectPanel(timeout) {
      if (timeoutId) {
        clearTimeout(timeoutId);
        timeoutId = null;
      }
      showInfo("hide");
      if (selectPopupPanel.style.display == "none") {
        return;
      }
      select.focus();
      selectCtrl.reset();
      selectPopupPanel.style.opacity = 0;
      document.body.style.overflowY = bodyOldOverflowY;
      if (timeout == 0) {
        selectPopupPanel.style.display = "none";
      }
      setTimeout(function () {
        selectPopupPanel.style.display = "none";
      }, isNaN(timeout) ? 200 : timeout);
    }

    function createPopupPanel() {
      select.insertAdjacentHTML("afterend",
        '<div class="selectPanelPosition">' +
        '<div class="selectPopupPanel" tabindex="9999">' +
        '    <div class="selectOptions"></div>' +
        '    <div class="scrollBar">' +
        '      <div class="slidBlock"></div>' +
        '    </div>' +
        '</div></div>');

      let options = select.options;
      let optionsCount = options.length;
      for (let i = 0; i < optionsCount; i++) {
        options[i].style.display = "none";
      }
    }

    function initSelectList(select, selectPopupPanel) {
      let MAX_HEIGHT = Math.ceil(window.screen.availHeight * 0.618);
      let options = select.options;
      let optionsCount = options.length;
      if (optionsCount == 0) {
        return;
      }

      let optionTextArr = new Array(options.length);
      let selectOptionsPanel = selectPopupPanel.getElementsByClassName("selectOptions")[0];
      let divOptionsHtml = "";
      let chars = {};
      for (let i = 0; i < optionsCount; i++) {
        divOptionsHtml +=
          '<div data="' + options[i].value + '" class="option">' +
          '    <div class="tag"></div>' +
          '    <div class="optionText">' + options[i].text + '</div>' +
          '</div>';
        options[i].style.display = "none";
        optionTextArr[i] = options[i].text.toUpperCase();
      }
      selectOptionsPanel.innerHTML = divOptionsHtml;

      for (let i = 0; i < optionsCount; i++) {
        let text = optionTextArr[i];
        for (let j = 0; j < text.length; j++) {
          let char = text[j];
          if (chars[char] && chars[char].indexOf(i) === -1) {
            chars[char].push(i);
          } else {
            chars[char] = [i];
          }
        }
      }

      let charsKeys = Object.keys(chars);
      for (let i = 0; i < charsKeys.length; i++) {
        let char = charsKeys[i];
        chars[char].sort(function (a, b) {
          return optionTextArr[a].indexOf(char) - optionTextArr[b].indexOf(char);
        });
      }

      let scrollBar = selectPopupPanel.getElementsByClassName("scrollBar")[0]
      let slidBlock = selectPopupPanel.getElementsByClassName("slidBlock")[0];
      let divOptions = selectOptionsPanel.getElementsByClassName("option");
      let panelHeight = 0;
      let lastSelectedIndex = -1;
      let lastFoucsIndex = -1;
      let lastKeyCode = null;
      let nextIndex = 0;

      slidBlock.onmousedown = function (event) {
        let shade = document.createElement("div");
        shade.style.position = "fixed";
        shade.style.left = "0";
        shade.style.top = "0";
        shade.style.right = "0";
        shade.style.bottom = "0";
        shade.style.zIndex = 999999;
        document.body.appendChild(shade);

        let slidBlockDrapY = event.clientY;
        let top = Number.parseInt(slidBlock.style.marginTop);
        let slidHeight = Number.parseInt(slidBlock.style.height);
        let scorllBarRange = panelHeight - slidHeight;
        let scorllRange = selectOptionsPanel.scrollHeight - scrollBar.getClientRects()[0].height;
        let mousemoveHandle = function (e) {
          let offset = e.clientY - slidBlockDrapY + top;
          if (offset >= -10 && offset <= scorllBarRange + 10) {
            selectOptionsPanel.scrollTop = Number.parseInt(1.0 * offset / scorllBarRange * scorllRange);
          }
        };
        let releaseHandle = function () {
          shade.removeEventListener("blur", this);
          shade.removeEventListener("nouseup", this);
          shade.removeEventListener("mousemove", mousemoveHandle);
          shade.remove()
        };

        shade.addEventListener("blur", releaseHandle);
        shade.addEventListener("mouseup", releaseHandle);
        shade.addEventListener("mousemove", mousemoveHandle);
      };

      let moveFun = function (index, selected, moveScroll) {
        if (index < 0 || index > optionsCount) {
          index = 0;
        }

        if (moveScroll) {
          let optionHeight = divOptions[0].clientHeight;
          let optionY = index * optionHeight;
          if (optionY < selectOptionsPanel.scrollTop) {
            selectOptionsPanel.scrollTop = optionY;
          } else if (optionY + optionHeight > selectOptionsPanel.scrollTop + panelHeight) {
            selectOptionsPanel.scrollTop = optionY + optionHeight - panelHeight;
          }
        }

        if (lastFoucsIndex >= 0 && lastFoucsIndex != lastSelectedIndex) {
          divOptions[lastFoucsIndex].setAttribute("class", "option");
        }
        lastFoucsIndex = index;

        if (selected) {
          if (lastSelectedIndex >= 0) {
            divOptions[lastSelectedIndex].setAttribute("class", "option");
          }
          divOptions[index].setAttribute("class", "option optionSelected");
          lastSelectedIndex = index;
        } else {
          if (index != lastSelectedIndex) {
            divOptions[index].setAttribute("class", "option optionFoucs");
          }
        }
      };

      let calcRect = function () {
        let marginTop = 0;
        let showScrollBar = false;
        let selectBottom = select.getClientRects()[0].y + select.clientHeight;
        let optionHeight = divOptions[0].clientHeight;
        let windowHeight = window.innerHeight;

        panelHeight = selectOptionsPanel.scrollHeight;
        if (panelHeight > MAX_HEIGHT) {
          panelHeight = MAX_HEIGHT;
          showScrollBar = true;
        }
        if (windowHeight - 15 - selectBottom < panelHeight) {
          if (selectBottom - select.clientHeight > windowHeight / 2) {
            let maxHeight = selectBottom - select.clientHeight - optionHeight;
            if (panelHeight > maxHeight) {
              panelHeight = maxHeight;
              showScrollBar = true;
            }
            marginTop = -panelHeight - select.clientHeight - 3;
          } else {
            panelHeight = windowHeight - selectBottom - optionHeight;
            showScrollBar = true;
          }
        }

        selectPopupPanel.style.width = select.clientWidth - 2 + "px";
        selectPopupPanel.style.marginTop = marginTop + "px";
        selectOptionsPanel.style.height = panelHeight + "px";

        if (showScrollBar) {
          scrollBar.style.display = "block";
          scrollBar.style.height = panelHeight + "px";
          let scrollHandle = function () {
            let scale = 1.0 * panelHeight / selectOptionsPanel.scrollHeight;
            let scrollBarHeight = panelHeight * scale;
            let scrollBarTop = selectOptionsPanel.scrollTop * scale;
            slidBlock.style.marginTop = scrollBarTop + "px";
            slidBlock.style.height = scrollBarHeight + "px";
          };
          selectOptionsPanel.scrollTop = 0;
          selectOptionsPanel.onscroll = scrollHandle;
          scrollHandle();
        } else {
          scrollBar.style.display = "none";
        }
        moveFun(select.selectedIndex, true, false);
        let offset = select.selectedIndex * optionHeight + (optionHeight - panelHeight) / 2;
        if (offset > selectOptionsPanel.scrollHeight - panelHeight) {
          offset = selectOptionsPanel.scrollHeight - panelHeight;
        }
        selectOptionsPanel.scrollTop = offset;
      };

      let changeSelectValue = function (value) {
        select.value = value;
        select.dispatchEvent(new Event("change"));
        moveFun(select.selectedIndex, true, true);
      };

      let calcIndex = function (e) {
        return Math.floor(optionsCount * (e.clientY - selectOptionsPanel.getClientRects()[0].y + selectOptionsPanel.scrollTop) / selectOptionsPanel.scrollHeight);
      };

      let keyDownHandle = function (event) {
        let e = event || arguments.callee.caller.arguments[0];
        if (!e) {
          return;
        }
        let keyCode = e.keyCode;
        if (keyCode == 27) {
          selectPopupPanel.blur();
          return;
        }

        let lastTime = selectPopupPanel.lastTime || 0;
        let nowTime = new Date().getTime();
        if (nowTime - lastTime < 80) {
          return;
        }
        selectPopupPanel.lastTime = nowTime;

        let index = lastFoucsIndex;
        if (keyCode == 38) {
          if (index > 0) {
            moveFun(--index, false, true);
          }
        } else if (keyCode == 40) {
          if (index + 1 < optionsCount) {
            moveFun(++index, false, true);
          }
        } else if (keyCode == 13 || keyCode == 32) {
          changeSelectValue(options[index].value);
          selectPopupPanel.blur();
        } else {
          let minPos = Number.MAX_VALUE;
          let indexArr = chars[String.fromCharCode(keyCode)];
          if (indexArr) {
            if (nextIndex >= indexArr.length || lastKeyCode != keyCode) {
              nextIndex = 0;
            }
            index = indexArr[nextIndex++];
          }
          moveFun(index, false, true);
        }
        lastKeyCode = keyCode;
      };

      let reset = function () {
        moveFun(lastSelectedIndex, false, false);
        lastKeyCode = 0;
        nextIndex = 0;
        lastFoucsIndex = -1;
      }

      selectOptionsPanel.onclick = function (e) {
        let index = calcIndex(e);
        if (index >= optionsCount) {
          index = optionsCount - 1;
        }
        changeSelectValue(options[index].value);
        selectPopupPanel.blur();
      };

      selectPopupPanel.onblur = function () {
        hideSelectPanel();
      };

      selectOptionsPanel.onmousemove = function (e) {
        let index = calcIndex(e);
        if (index < optionsCount && index != lastFoucsIndex) {
          moveFun(index, false);
        }
      };

      selectPopupPanel.onkeydown = function (event) {
        keyDownHandle(event);
      };
      selectPopupPanel.onkeyup = function (event) {
        selectPopupPanel.lastTime = new Date().getTime;
      };
      return {calcRect: calcRect, changeSelectValue: changeSelectValue, move: moveFun, reset: reset};
    }
  }

  function restore(value) {
    let select = null;
    if (typeof value == "string") {
      select = document.getElementById(value);
    } else {
      select = value;
    }

    let options = select.options;
    let optionsCount = options.length;
    for (let i = 0; i < optionsCount; i++) {
      options[i].style.display = "block";
    }

    if (select) {
      let selectPanelPosition = select.parentNode.getElementsByClassName("selectPanelPosition")[0];
      if (selectPanelPosition) {
        selectPanelPosition.remove();
      }

      if (select.handles) {
        Object.keys(select.handles).forEach(function (key) {
          select.removeEventListener(key, select.handles[key]);
        });
      }
    }
  }

  var s = document.getElementById("s1");
  initSelect(s);
  s.onchange = function (e) {
    showInfo("select:" + e.target.value);
  }

  c1.onchange = function () {
    if (c1.checked) {
      initSelect("s1");
    } else {
      restore("s1");
    }
  }

  function showInfo(text) {
    if (!info.textContent.endsWith(":")) {
      info.textContent += ", ";
    }
    info.textContent += text;
    if (info.scrollHeight > 300) {
      info.scrollTop = info.scrollHeight - 300;
    }
  }

  info.ondblclick = function () {
    info.textContent = "info:"
  };
</script>
</body>

</html>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章