H5移动端重写长按选中复制功能

背景:

在平板浏览器里,长按选中状态后,出现复制搜索菜单,是浏览器的默认行为

现在的需求是,用户长按文字,选中文字,可划选改变选中,出现搜索按钮,点击搜索按钮,页面进入搜索

本来想使用这个禁用,经测试,无效

  -webkit-touch-callout: none;

  -webkit-user-select: none;

  -khtml-user-select: none;

  -moz-user-select: none;

  -ms-user-select: none;

  user-select: none;

阻止浏览器的默认行为,只能通过监听touchstart事件,在事件中 e.preventDefault();这样,那个弹窗就不出来了,但是选中状态也没有了,只能重写, 那只能进行重写文字复制功能,监听触摸事件,模拟长按

<template>
  <div class="text_area">
    <div class="text-container" @touchstart="mousedownHandler" @touchmove="mouseMoveHandler" @touchend="mouseupHandler" ref="reference">
      <span class="text_value">{{value}}</span>
    </div>

  </div>

</template>

 利用 document["caretRangeFromPoint"](x, y) 根据触摸的点位置,设置选中range,设置手柄的位置,监听手柄的移动事件,重新设置选中,根据选区,微调手柄位置,使手柄不能放在文字上

export default {
  props: {
    value: {
      type: String,
    },
  },
  data() {
    return {
      mouseStatus: "none",
      showMenu: false,
      menuPosition: {},
      selectText: "",
      toucheX: "",
      toucheY: "",
      Loop: 0,
      visible: false,
      range: "",
      textNode: "",
    };
  },
  mounted() {
    const This = this;
    document.addEventListener("touchstart", This.destroyDiv);
  },
  destroyed() {
    const This = this;
    document.removeEventListener("touchstart", This.destroyDiv);
    This.destroyDiv();
  },

  methods: {
    initData() {},
    mousedownHandler(e) {
      console.log(e);
      e.preventDefault();
      const This = this;
      this.toucheX = e.targetTouches[0].clientX;
      this.toucheY = e.targetTouches[0].clientY;
      clearTimeout(this.Loop); //再次清空定时器,防止重复注册定时器
      this.Loop = setTimeout(
        function () {
          This.selectCursorWord(e.targetTouches[0]);
        }.bind(this),
        500
      );
      console.log("touchstart事件触发了222");
      // 清除全局下的回复弹框
    },
    mouseMoveHandler(e) {
      console.log("mousemove");
      this.mouseStatus = "mousemove";
      const moveX = e.targetTouches[0].clientX;
      const moveY = e.targetTouches[0].clientY;
      // 解决vivo机型,手指没有move,touchmove事件仍然会调用而导致setTimeout被clear
      if (this.toucheX !== moveX || this.toucheY !== moveY) {
        // 手指滑动,清除定时器,中断长按逻辑
        this.Loop && clearTimeout(this.Loop);
      }
    },
    mouseupHandler(e) {
      console.log("mouseupHandler");
      clearTimeout(this.Loop); //清空定时器,防止重复注册定时器
    },
    destroyDiv() {
      const This = this;
      let achor = document.querySelector(".achor.before");
      let achor2 = document.querySelector(".achor.after");
      // 搜索
      const search = document.querySelector(".text_search_wrap");
      achor && document.body.removeChild(achor);
      achor2 && document.body.removeChild(achor2);
      search && document.body.removeChild(search);
      // 清除选中
      const sel = window.getSelection();
      sel.removeAllRanges();
    },

    selectCursorWord(e) {
      const x = e.clientX;
      const y = e.clientY;

      let offsetNode;
      let offset;

      const sel = window.getSelection();
      sel.removeAllRanges();

      if (document["caretPositionFromPoint"]) {
        const pos = document["caretPositionFromPoint"](x, y);
        if (!pos) {
          return;
        }
        offsetNode = pos.offsetNode;
        offset = pos.offset;
      } else if (document["caretRangeFromPoint"]) {
        const pos = document["caretRangeFromPoint"](x, y);
        console.log(pos, x, y);
        if (!pos) {
          return;
        }
        offsetNode = pos.startContainer;
        offset = pos.startOffset;
        console.log(pos, offset, offsetNode);
      } else {
        return;
      }

      if (offsetNode.nodeType === Node.TEXT_NODE) {
        const textNode = offsetNode;
        this.textNode = textNode;
        const content = textNode.data;
        const head = (content.slice(0, offset).match(/[-_a-z0-9]+$/i) || [
          "",
        ])[0];
        const tail = (content
          .slice(offset)
          .match(/^([-_a-z0-9]+|[\u4e00-\u9fa5])/i) || [""])[0];
        if (head.length <= 0 && tail.length <= 0) {
          return;
        }

        const range = document.createRange();
        this.range = range;
        range.setStart(textNode, offset - head.length);
        range.setEnd(textNode, offset + tail.length);
        const rangeRect = range.getBoundingClientRect();
        //以这个点为中心点,长20 宽20,看这个选中在不在这个范围里,增加灵敏度
        if (
          rangeRect.left <= x + 10 &&
          rangeRect.right >= x - 10 &&
          rangeRect.top <= y + 10 &&
          rangeRect.bottom >= y - 10
        ) {
          sel.addRange(range);
          this.showCursorAndPop();
        }

        range.detach();
      }
    },

    showCursorAndPop(rangeRect) {
      const rectList = this.range.getClientRects();
      console.log(rectList);
      //  选中多行时的处理
      let before = rectList[0],
        after = rectList[rectList.length - 1];

      let achor = document.querySelector(".achor.before");
      let achor2 = document.querySelector(".achor.after");

      const { top, left } = before;
      const { bottom, right, height } = after;
      console.log(before, after);
      if (top == bottom - height && left == right) {
        // 重合移除
        this.destroyDiv();
        return;
      }
      if (achor) {
        achor.style.cssText = `top:${top}px;left:${left}px;`;
      } else {
        achor = document.createElement("div");
        achor.className = "achor before";
        achor.style.cssText = `top:${top}px;left:${left}px;`;
        document.body.appendChild(achor);
        this.addCursorEvent(achor, "before");
      }
      if (achor2) {
        achor2.style.cssText = `top:${bottom - height}px;left:${right}px;`;
      } else {
        achor2 = document.createElement("div");
        achor2.className = "achor after";
        achor2.style.cssText = `top:${bottom - height}px;left:${right}px;`;
        document.body.appendChild(achor2);
        this.addCursorEvent(achor2, "after");
      }

      // 搜索
      let search = document.querySelector(".text_search_wrap");

      if (search) {
        search.style.cssText = `top:${top - 18}px;left:${
          (left + right) / 2
        }px;`;
      } else {
        search = document.createElement("div");
        search.innerHTML = "搜索";
        search.addEventListener("touchstart", this.remarkClick);
        search.className = "text_search_wrap";
        search.style.cssText = `top:${top - 18}px;left:${
          (left + right) / 2
        }px;`;
        document.body.appendChild(search);
      }
    },
    addCursorEvent(element, type) {
      let move = false;
      let currentX, currentY;

      const textRect = this.$el
        .querySelector(".text_value")
        .getBoundingClientRect();
      console.log(textRect);
      element.addEventListener("touchstart", (e) => {
        e.preventDefault();
        e.stopPropagation();
        console.log(e.targetTouches[0]);
        move = true;
        const { top, left } = element.style;
        currentX = e.targetTouches[0].clientX - parseInt(left);
        currentY = e.targetTouches[0].clientY - parseInt(top);
        console.log(currentX, currentY);
      });
      element.addEventListener("touchmove", (e) => {
        if (move) {
          //
          const top = e.targetTouches[0].clientY - currentY;
          const left = e.targetTouches[0].clientX - currentX;
          // 判断移动是否移出了text_value
          if (top >= textRect.top && left >= textRect.left) {
            // 判断before 不能超过after  after 不能超过before

            this.setCusorPosition(
              element,
              e.targetTouches[0].clientX,
              e.targetTouches[0].clientY,
              type
            );
          }

          //element.style.cssText = `top:${top}px;left:${left}px;`;
        }
      });
      element.addEventListener("touchend", () => {
        move = false;
      });
    },
    setCusorPosition(element, x, y, type) {
      // 一定要先隐藏,不然选中的不是文字,是这个手柄
      element.style.display = "none";
      let offsetNode, offset;
      if (document["caretRangeFromPoint"]) {
        const pos = document["caretRangeFromPoint"](x, y);
        if (!pos) {
          return;
        }
        offsetNode = pos.startContainer;
        offset = pos.startOffset;
        console.log(pos, 333);
      }

      if (offsetNode.nodeType === Node.TEXT_NODE) {
        const range = this.range;
        const textNode = offsetNode;

        if (type == "before") {
          if (offset > range.endOffset) {
            range.setStart(textNode, range.endOffset);
            range.setEnd(textNode, offset);
          } else if (offset == range.endOffset) {
            range.setStart(textNode, offset - 1);
          } else {
            range.setStart(textNode, offset);
          }
        } else {
          if (offset < range.startOffset) {
            range.setStart(textNode, offset);
            range.setEnd(textNode, range.startOffset);
          } else if (offset == range.startOffset) {
            range.setEnd(textNode, offset + 1);
          } else {
            range.setEnd(textNode, offset);
          }
        }
        const rangeRect = range.getBoundingClientRect();

        this.showCursorAndPop(rangeRect);

        range.detach();
        element.style.display = "block";
      }
    },

    remarkClick(e) {
      e.preventDefault();
      e.stopPropagation();
      const This = this;
      //点击搜索的一瞬间,失焦
      // 第一步:获取当前鼠标框选的选区(Selection)以及当前选区的Range
      const selectText = window.getSelection().toString()
        ? window.getSelection().toString()
        : this.mobileSelectContent
        ? this.mobileSelectContent
        : ""; // 获取当前window滑选的文字

      console.log("点击搜索", selectText);

      this.destroyDiv();
      this.$emit("search", selectText);
    },
  },
};

 

<style>
.text_search_wrap {
  background: #fff;
  border-radius: 4px;
  padding: 3px 8px;
  color: #666;
  font-size: 12px;
  position: absolute;
  box-shadow: 0 0 1px 1px rgba(162, 162, 162, 0.6);
  transform: translate(-50%, -100%);
}
.achor {
  width: 2px;
  height: 14px;
  background: blue;
  position: absolute;
  &::after {
    content: "";
    position: absolute;
    top: 0;
    width: 10px;
    height: 10px;
    border: 2px solid blue;
    border-radius: 10px;
    left: 50%;
    transform: translate(-50%, -100%);
  }
}
.after {
  &::after {
    top: auto;
    bottom: 0;
    border: 2px solid blue;
    transform: translate(-50%, 100%);
  }
}
</style>

至此 功能完成

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值