jQuery实现文本框@功能简单版

307 阅读2分钟

前端实现常见的输入框@功能,或是插入话题功能,我们先只考虑textarea控件中的实现思路,难点是文本框的光标位置,在整个页面中的坐标获取。

网上搜了一圈,整个位置的获取是通过克隆一个div,把文本框的内容实时同步更新到div中,光标的位置用一个span标签表示,然后获取span标签的offset位置,从而计算出文本框中真实光标的位置。

代码如下:自行运行,不是很完善,感兴趣的可二次加工,因为有更好的方案插件,这个只作为技术调研。

css

` * { margin: 0; padding: 0; } body { font-size: 12px; } ul, li { list-style: none; }

  #txtDom,
  #clonetxt {
    width: 200px;
    height: 60px;
    /* font-size: 14px; */
    line-height: 140%;
    padding: 5px;
    /* 字体保持一致 否则 */
    /* font-family: auto; */
    word-break: break-all;
    resize: none;
    overflow-y: auto;
  }

  ::-webkit-scrollbar {
    -webkit-appearance: none;
    width: 7px;
  }
  ::-webkit-scrollbar-thumb {
    border-radius: 4px;
    background-color: rgba(0, 0, 0, 0.5);
  }

`

html

`

<div style="width: 300px; margin: 0 auto; padding: 100px 0 0">
  <textarea id="txtDom"></textarea>

  <div id="clonetxt" style="background: #ccc; position: absolute"></div>

  <div
    id="tipsAtDom"
    style="
      border: 1px solid #f60;
      color: #999;
      background: #fff;
      position: absolute;
      display: none;
    "
  >
    <input type="text" placeholder="可搜索" />
    <ul>
      <li>小明</li>
      <li>李磊</li>
      <li>韩梅梅</li>
    </ul>
  </div>
</div>



<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>

js

` var txtDom = $("#txtDom");

  var tipsAtDom = $("#tipsAtDom");
  var cloneDiv = $("#clonetxt");
  cloneDiv.css({
    width: txtDom.width(),
    height: "auto",
    "font-size": txtDom.css("font-size"),
    "font-family": txtDom.css("font-family"),
  });

  var gbspan = '<span class="gbspan" style="color:red;">|</span>';

  // 浮层备选点击事件
  tipsAtDom.on("click", "li", function () {
    var domVal = txtDom.val();
    var txt = $(this).text();
    const { selectionStart, selectionEnd } = txtDom[0];
    const newStr = `${domVal.substring(
      0,
      selectionStart
    )}${txt} ${domVal.substring(selectionEnd, domVal.length)}`;
    txtDom.val(newStr);

    tipsAtDom.hide();

    setCursorPosition(txtDom[0], selectionStart + txt.length + 1);
    // txtDom.focus() //上面方法已经执行了focus

    // 定位滚动条到之前 因为focus会让滚动条触底 keyup是为了让clone div光标位置更新
    txtDom.scrollTop(txtDom.data("scoltop")).trigger("keyup");
  });

  // 文本框事件绑定
  txtDom
    // 键盘抬起
    .on("keyup click", function (e) {
      $(this).data("scoltop", $(this).scrollTop());
      this.startPos = e.target.selectionStart;
      this.endPos = e.target.selectionEnd;
      const originalStr = e.target.value;
      const htmlStr =
        this.selectionStart === e.target.textLength
          ? `${originalStr}${gbspan}`
          : `${originalStr.substring(
              0,
              this.startPos
            )}${gbspan}${originalStr.substring(
              this.endPos,
              originalStr.length
            )}`;

      cloneDiv.html(htmlStr.replace(/\n/g, "<br/>"));
      // console.log(this.value);
    })
    // 键盘按下
    .on("keydown", function (e) {
      var { key, keyCode } = e;
      console.log("key:", key, "keyCode:", keyCode);

      // console.log(e);
      // console.log(this.value)
      if (["@", "#"].includes(key)) {
        console.log("ok @");

        tipsAtDomShow();
      } else {
        tipsAtDom.hide();
      }
    });

  // 显示候选框 设置pos位置
  function tipsAtDomShow() {
    const $span = cloneDiv.find(".gbspan:first");
    var gbPos = $span.length ? $span.position() : { left: 0, top: 0 };
    const txtDomPos = txtDom.offset();

    console.log("gbPos:", gbPos, txtDomPos);
    tipsAtDom
      .css({
        left: txtDomPos.left + gbPos.left + 12 + "px",
        top: txtDomPos.top + gbPos.top + 25 - txtDom.scrollTop() + "px",
      })
      .show();
  }

  /**
   * 文本框根据输入内容自适应高度
   * @param                {HTMLElement}        输入框元素
   * @param                {Number}                设置光标与输入框保持的距离(默认0)
   * @param                {Number}                设置最大高度(可选)
   */
  var autoTextarea = function (elem, extra, maxHeight) {
    extra = extra || 0;
    var isFirefox =
        !!document.getBoxObjectFor || "mozInnerScreenX" in window,
      isOpera =
        !!window.opera && !!window.opera.toString().indexOf("Opera"),
      addEvent = function (type, callback) {
        elem.addEventListener
          ? elem.addEventListener(type, callback, false)
          : elem.attachEvent("on" + type, callback);
      },
      getStyle = elem.currentStyle
        ? function (name) {
            var val = elem.currentStyle[name];

            if (name === "height" && val.search(/px/i) !== 1) {
              var rect = elem.getBoundingClientRect();
              return (
                rect.bottom -
                rect.top -
                parseFloat(getStyle("paddingTop")) -
                parseFloat(getStyle("paddingBottom")) +
                "px"
              );
            }

            return val;
          }
        : function (name) {
            return getComputedStyle(elem, null)[name];
          },
      minHeight = parseFloat(getStyle("height"));

    elem.style.resize = "none";

    var change = function () {
      var scrollTop,
        height,
        padding = 0,
        style = elem.style;

      if (elem._length === elem.value.length) return;
      elem._length = elem.value.length;

      if (!isFirefox && !isOpera) {
        padding =
          parseInt(getStyle("paddingTop")) +
          parseInt(getStyle("paddingBottom"));
      }
      scrollTop =
        document.body.scrollTop || document.documentElement.scrollTop;

      elem.style.height = minHeight + "px";
      if (elem.scrollHeight > minHeight) {
        if (maxHeight && elem.scrollHeight > maxHeight) {
          height = maxHeight - padding;
          style.overflowY = "auto";
        } else {
          height = elem.scrollHeight - padding;
          style.overflowY = "hidden";
        }
        style.height = height + extra + "px";
        scrollTop += parseInt(style.height) - elem.currHeight;
        document.body.scrollTop = scrollTop;
        document.documentElement.scrollTop = scrollTop;
        elem.currHeight = parseInt(style.height);
      }
    };

    addEvent("propertychange", change);
    addEvent("input", change);
    addEvent("focus", change);
    change();
  };

  /*
   * 设置输入域(input/textarea)光标的位置
   * @param {HTMLInputElement/HTMLTextAreaElement} elem
   * @param {Number} index
   */
  function setCursorPosition(elem, index) {
    var val = elem.value;
    var len = val.length;

    // 超过文本长度直接返回
    if (len < index) return;
    // setTimeout(function() {
    elem.focus();
    if (elem.setSelectionRange) {
      // 标准浏览器
      elem.setSelectionRange(index, index);
    } else {
      // IE9-
      var range = elem.createTextRange();
      range.moveStart("character", -len);
      range.moveEnd("character", -len);
      range.moveStart("character", index);
      range.moveEnd("character", 0);
      range.select();
    }
    // }, 10)
  }

  autoTextarea(txtDom[0], 0, 200);

`