【selection】 学习光标API并实现编辑区插入表情图片的功能

news2024/10/6 12:23:37

目录

  • 场景介绍
  • selection介绍
  • selection API
  • range 介绍
  • range API
  • 实现编辑区插入表情图片
  • 参考资料


场景介绍

在写web版聊天器时,遇到一个需求:

聊天时用户可以在编辑区加入表情图片,并且表情图片要插入在光标位置。

// *web版聊天器地址:
http://81.68.151.74/ (开发中,欢迎讨论)

就像这样:
在这里插入图片描述

刚开始没考虑这块内容,所以用的<textarea>标签,

而在该标签内,是无法显示出插入表情图片的。

这就需要用到HTML中的contenteditable="true"属性,来创建一个自定义编辑区。

就像这样:

<div class="box" contenteditable="true"></div>

然后对该自定义编辑区进行加工。

此过程中,需要根据用户点击的位置(即光标显示的位置)来显示插入的图片,

这就需要用到光标的控制。


selection介绍

光标控制的API分为两块内容:selection选区和range片段。

根据官方介绍,selection 对象所对应的是用户所选择的 ranges(区域),俗称拖蓝

默认情况下,该函数只针对一个区域。

因为一般情况下,用拖动鼠标只能同时选中一块区域。

在使用js脚本的情况下,range可能会出现多个。

因此,后者可以认为是前者的一部分,选区包含若干片段,一般是一个。

本篇就梳理下两者的常用API,理清操作流程。


selection API

selection 对象表示用户选择的文本范围或插入符号的当前位置。

它代表页面中的文本选区,可能横跨多个元素。文本选区由用户拖拽鼠标经过文字而产生。

获取selection

var selection = window.getSelection();

在这里插入图片描述

下面依次介绍selection的相关属性和方法。


首先需要注意的是,以下内容提到的起点终点,并不是固定的。

这和鼠标从左往右选择还是从右往左选择有关系。

先从哪里开始,哪里就是起点。


<!--属性-->
1. selection.anchorNode 
只读属性,用于返回选区开始位置所属的节点。

2. selection.anchorOffset
只读属性,返回选区的锚节点( Selection.anchorNode)起点偏移量的数字。
返回值从零开始计数,如果选区从锚节点(Selection.anchorNode)的第一个字符开始,
返回值为 0。

3. selection.focusNode
只读属性,返回所选内容的结束位置部分所属的节点。

4. selection.focusOffset
返回选区终点(鼠标松开瞬间所记录的那个点)在焦点(Selection.focusNode)中的偏移量。
返回值从零开始计数,如果选区(Selection)在焦点(Selection.focusNode)的第一个字符前结束,
返回值为 0。

5. selection.isCollapsed
返回一个布尔值,用于描述选区的起始点和终止点是否位于一个位置,即是否框选了。

6. selection.rangeCount
只读属性。用于返回选区 (selection) 中 range 对象数量
一般rangeCount总是为1,因为拖动鼠标只能选择一个区域。

7. selection.type
只读属性,用于描述当前选区的选择类型。
结果有三个值:
- `None` 当前没有选择。
- `Caret`: 选区已折叠(即 光标在字符之间,并未处于选中状态)。
- `Range`: 选择的是一个范围。即框选了内容。

<!--方法-->
1. selection.addRange(range)
向选区(Selection)中添加一个区域(Range)

2. selection.collapse(node, offset)
设置光标在选区内的位置。
node是光标所落在的目标节点
offset是落在节点的偏移量

3. selection.collapseToEnd()
取消当前选区,并把光标定位在原选区的最末尾处。无参数

4. selection.collapseToStart()
取消当前选区,并把光标定位在原选区的最开始处。无参数

5. selection.containsNode(node,isPartlyContained)
node是要被判断的节点
isPartlyContained表示node节点是否被包含
isPartlyContained为true时,就是要判断选区是否包含node,(此时包含一部分或者全部,都算被包含)
isPartlyContained为false时,就是要判断选区是否包含node的全部

6. selection.deleteFromDocument()
删除选区

7. selection.extend(node, offset)
移动选中区的焦点到指定的点。选中区的锚点不会移动。选中区将从锚点开始到新的焦点,不管方向。
node是表示焦点会被移动到node节点内
offset 可选参数,默认为0.表示偏移量

8. var range = selection.getRangeAt(index)
返回一个包含当前选区内容的区域对象
index表示区域编号,从0开始。index永远要小于rangeCount
rangeCount一般为1,所以index一般是0

9. selection.modify(alter, direction, granularity)
通过简单的文本命令来改变当前选区或光标位置。
alter 改变类型。传入 "move" 来移动光标位置,或者 "extend" 来扩展当前选区。
direction 调整选区的方向。forward backward left right
granularity 调整的距离颗粒度。一次是移动一个字的举例,还是一个字符的距离等。

10. selection.removeAllRanges()
从当前 selection 对象中移除所有的 range 对象,
取消所有的选择只 留下anchorNode 和focusNode属性并将其设置为 null
无参数。

11. selection.removeRange(range)
将一个区域从选区中移除。
range 表示要被移除的区域

12. selection.selectAllChildren(parentNode)
所有 parentNode 元素的子元素会被设为选中区域,
并取消之前的选中区域,parentNode 本身除外。

13. selection.setBaseAndExtent(anchorNode,anchorOffset,focusNode,focusOffset)
用来选中并设置在两个特定的 DOM 节点中文本选中的范围,
并且选中的任何内容都位于两个节点之间。
anchorNode 锚节点 - 选中内容的开始节点
anchorOffset 选中范围内起点位置在锚节点下第几个子节点的位置。
例如,如果是值为 0 的话,整个节点都是被选中的。
如果值为 1 的话,那么至少整个节点至少有一个子节点被选中。以此类推。
focusNode 焦点节点 - 选中内容的结尾节点
focusOffset 选中范围内结束位置在焦点节点下第几个子节点的位置。
例如,如果是值为 0 的话,整个节点都是被选中的。
如果值为 1 的话,那么至少整个节点至少有一个子节点被选中。以此类推

range 介绍

range表示一部分文档片段。

可以通过多种方法创建或获取:

// 第一种 从selection中获取
var range = selection.getRangeAt();

// 第二种 通过Range创建
var range = new Range()

// 第三种 create创建
var range = document.createRange()

// 注意:
// 如果是创建的range,在使用他的大多数方法之前需要去设置他的临界点。
// 也就是要确定光标

range API

<!--属性-->
1. range.collapsed
返回一个布尔值。表示是否起始点和结束点是同一个位置
如果返回true,就表示起始点和结束点重合。
如果返回false,就表示起始点和结束点没重合。

2. range.endContainer
它会返回Range对象结束的Node

3. range.endOffset
返回代表 Range 结束位置在 Range.endContainer 中的偏移值的数字

4. range.startContainer
返回 Range 开始的节点

5. range.startOffset
用于返回一个表示 Range 在 startContainer 中的起始位置的数字

<!--方法-->
1. range.cloneContents()
克隆一个区域的所有节点。
节点的id属性也会被克隆,这点需要注意。
另外,节点的监听事件不会被克隆

2. range.cloneRange()
克隆一个和原有range无关的range对象

3. range.collapse(toStart)
用于折叠边界点。也就是 是否让光标重合
toStart为true,表示让光标重合,并在range的起始点出现
toStart为false,表示让光标重合,并在range的结束点出现

4. range.insertNode(newNode);
在Range的起始位置插入节点
newNode是要插入的节点

5. range.intersectsNode(node)
用来判断给定的节点node是否与range相交,返回一个布尔值

6. range.selectNode(node);
将 Range 设置为包含整个 node 及其内容。
Range 的起始和结束节点的父节点与 node 的父节点相同。

7. range.selectNodeContents(referenceNode);
用于设置 Range,使其包含一个 Node 的内容

8. range.setEnd(endNode, endOffset);
设置光标的结束点

9. range.setEndAfter(referenceNode)
设置光标的结束点在node节点之后

10. range.setEndBefore(referenceNode)
设置光标的结束点在node节点之前

11. range.setStart(startNode, startOffset);
设置 Range的开始位置

12. range.setStartAfter(referenceNode)
设置range的开始位置在node节点之后

13. range.setStartBefore(referenceNode)
设置range的开始位置在node节点之前

14. range.surroundContents(newParent);
将 Range 对象的内容移动到一个新的节点,并将新节点放到这个范围的起始处。

实现编辑区插入表情图片

index.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
	<style>
		* {
			padding: 0;
			margin: 0;
		}
		.wrap {
			position: relative;
			width: 500px;
			height: 200px;
			overflow: hidden;
			overflow-y: auto;
			margin-left: 100px;
			background-color: #f2a5a5;
			margin-top: 20px;
			box-sizing: border-box;
			padding: 10px;
		}
		.wrap .box {
			width: 100%;
			border: none;
			line-height: 28px;
		}
		.show {
			width: 500px;
			height: 200px;
			margin-left: 100px;
			border: none;
			border: 1px solid #000;
			line-height: 28px;
			margin-top: 20px;
		}
		.box[contenteditable] {
			outline: none;
		}
		.box img, .show img {
			width: 28px;
			vertical-align: middle;
		}
		.btn, .show_btn {
			width: 60px;
			height: 30px;
			text-align: center;
			line-height: 30px;
			margin-left: 300px;
			margin-top: 20px;
		}
	</style>
</head>
<body>
	<div class="wrap">
		<div class="box" contenteditable="true">
		</div>
	</div>
	<button class="btn">插入表情</button>
	<div class="show"></div>
	<button class="show_btn">显示内容</button>
	<script src="./index.js"></script>
</body>
</html>

index.js

var wrap = document.querySelector('.wrap'); // 编辑框的外层
var box = document.querySelector('.box'); // 编辑框
var sendEmoji = document.querySelector('.btn'); // 发送按钮
var img = document.createElement('img'); // 要插入的表情img
img.src = '	https://www.kktv5.com/images/facepic/good.gif';
var currentRange; // 记录光标位置

// 点击编辑框时,记录下光标位置
box.onclick = function () {
  var selection = window.getSelection();
  currentRange = selection.getRangeAt(0);
}
// 上下左右键 enter 输入的内容等键也会改变光标位置,也要记录下来
box.onkeyup = function () {
  var selection = window.getSelection();
  currentRange = selection.getRangeAt(0);
}

// 插入表情
sendEmoji.onclick = function () {
  box.focus();
  // 创建一个新的选区
  var selection = window.getSelection();
  // 如果光标位置原来就存在,就用原来的。
  // 原来不存在,就重新创建一个
  var range = currentRange ?? selection.getRangeAt(0);
  // 插入表情图标
  range.insertNode(img.cloneNode());
  // 插入后,光标显示在表情图片后面
  range.collapse();
  // 移除其他的区域
  selection.removeAllRanges();
  // 把带有表情图片的区域插入到选区内
  selection.addRange(range);
}

var show = document.querySelector('.show');
var show_btn = document.querySelector('.show_btn');

show_btn.onclick = function () {
  // 把编辑区的内容展示到另一个地方
  // 这里是用来模拟的接收人看到的结果
  show.innerHTML = box.innerHTML;
}

// 用一层wrap包裹住编辑区,目的是解决光标无法自动捕捉表情图片的问题
// 这里点击wrap,让光标也能识别表情图片
wrap.onclick = function () {
  var selection = window.getSelection();
  if (currentRange) {
    selection.removeAllRanges();
    selection.addRange(currentRange);
  } else {
    selection.selectAllChildren(box);
    selection.collapseToEnd();
  }
}

参考资料

  • section
https://developer.mozilla.org/zh-CN/docs/Web/API/Selection
  • range
https://developer.mozilla.org/zh-CN/docs/Web/API/Range

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/30516.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

useMemo 使用误区

文章の目录问题背景useMemo 使用前后组件性能对比结论问题背景 在某一个h5项目中&#xff0c;使用了 useMemo 对项目中的组件进行优化&#xff0c;减少组件不必要的re-render, 优化后的结果&#xff1a; 在组件的props和状态未改变时&#xff0c;组件不再进行 re-render 表面上…

生意不好如何逆风翻盘 | 多门店经营必读技巧(1):导购管理 连锁店管理的技巧 连锁店生意经 如何做导购管理

很多连锁店老板反馈&#xff0c;为了优化门店的销售业绩&#xff0c;什么方法都试过了&#xff0c;改店铺陈列、搞优惠活动、做会员管理.......为什么分店的业绩还是看不到明显的提升&#xff1f; 方法试过了&#xff0c;结果没变化&#xff0c;那只能是执行这块出了问题&#…

量子计算(八):观测量和计算基下的测量

文章目录 观测量和计算基下的测量 一、观测量 二、计算基下的测量 三、投影测量 观测量和计算基下的测量 一、观测量 量子比特&#xff08;qubit&#xff09;不同于经典的比特&#xff08;bit&#xff09;&#xff0c;一个量子比特|>可以同时处于|0>和|1>两个状态…

Linux从入门到精通(八)——Linux磁盘管理

文章篇幅较长&#xff0c;建议先收藏&#xff0c;防止迷路 文章跳转Linux从入门到精通&#xff08;八&#xff09;——Linux磁盘管理goLinux从入门到精通&#xff08;九&#xff09;——Linux编程goLinux从入门到精通&#xff08;十&#xff09;——进程管理goLinux从入门到精…

C++ 结合mysql写一个服务端

1 libhv和mysql libhv是一个跨平台的C网络库。 mysql是一个关系型数据库。 2 下载MySQL&#xff0c; 最好不要下载高版本的&#xff0c;容易出错&#xff01;&#xff01;&#xff01; 下载地址MySQL 下载好后目录是这样的&#xff1a; 然后在环境变量里配置&#xff1a;…

Hive 3.1.3

1.下载安装包 Index of /hive/hive-3.1.3https://dlcdn.apache.org/hive/hive-3.1.3/ 2.安装&修改配置文件 2.1 安装MySQL a. 搜索centos7默认的mariadb & 卸载 [root@node1 ~]# rpm -qa | grep mariadb mariadb-libs-5.5.44-2.el7.centos.x86_64 卸载 [r…

【计算机组成原理Note】5.4.2 控制器-微程序

5.4.2 控制器-微程序 硬布线工作原理&#xff1a;微操作控制信号由组合逻辑电路根据当前的指令码、状态和时序&#xff0c;即时产生微程序工作原理&#xff1a;事先把微操作控制信号存储在一个专门的存储器(控制存储器)中&#xff0c;将每一条机器指令编写成一个微程序&#xf…

差钱吗?周杰伦线上演唱会没关打赏惹争议,看看同时直播的腾格尔

随着世界杯的到来&#xff0c;全球都进入了世界杯时间&#xff0c;音乐领域的明星们&#xff0c;都以各种形式欢迎世界杯的到来。在世界杯开幕的前一晚上&#xff0c;著名音乐人周杰伦&#xff0c;在某手平台开启了线上演唱会&#xff0c;吸引了众多人前来围观。 据不完全统计&…

【正点原子FPGA连载】 第七章 Verilog HDL语法 摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第七章 Verilog …

【计组】入门篇 --《深入浅出计算机组成原理》

课程链接&#xff1a;深入浅出计算机组成原理_组成原理_计算机基础-极客时间 目录 一、为什么需要学习计算机组成原理 二、冯诺依曼体系结构&#xff1a;计算机组成的金字塔 1、计算机的基本硬件组成 2、冯诺依曼体系结构 三、通过CPU主频谈性能 1、什么是性能 2、计算…

【微信早安定时推送消息】微信公众号定时推送早安消息 带天气、纪念日、生日、定时推送等(完整代码)

我挥舞着键盘和本子&#xff0c;发誓要把世界写个明明白白。 简介 利用所学知识给他/她一个惊喜&#xff0c;是作为计算机专业的你最大的乐趣。 无计算机基础&#xff0c;5分钟即设置好 &#xff08;定时推送 及 最新版 将在下期带来&#xff09; 获取完整代码&#xff0c;关注…

性能测试怎么做?性能测试重点和各项性能测试流程(超级详细)

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 性能测试基础 1&am…

【设计模式】过滤器模式(Filter Pattern)

过滤器模式属于结构型模式&#xff0c;但它并不属于我们常说的二十三种设计模式。主要是以相对解耦的方式来过滤一组对象。 文章目录过滤器模式的介绍优点应用场景过滤器模式的使用类图实现方法第一步&#xff0c;创建员工类第二步&#xff0c;创建过滤器接口第三步&#xff0c…

如何部署商城项目

如何部署商城项目 1.导入数据库 ​ 注意&#xff1a;先保证数据库开启了远程访问&#xff08;见数据库安装和配置笔记&#xff09;。 1.1设置数据库服务器编码 ​ vi /etc/my.cnf ​ 在[mysqld]下面添加 character_set_serverutf8 init_connectSET NAMES utf81.2打开本地…

RK3399平台开发系列讲解(中断篇)中断控制器(Generic Interrupt Controller)

🚀返回专栏总目录 文章目录 一、GIC硬件的实现形态二、主要的功能块三、中断类型四、中断状态沉淀、分享、成长,让自己和他人都能有所收获!😄 📢外围设备不是把中断请求直接发给处理器,而是发给中断控制器,由中断控制器转发给处理器。ARM公司提供了一种标准的中断控制…

NR CSI(二) the workflow of CSI report

微信同步更新欢迎关注同名modem协议笔记 本篇内容是对CSI report相关流程的整理&#xff0c;其描述主要集中在R16 38.331 38.321 38.214中&#xff0c;以实网中的一个配置开始&#xff0c;看下相关定义。 如上图实网中的配置&#xff0c;CSI-ReportConfig 对应的就是Reporting…

Cookie和Session的工作流程是什么样的?5min学懂:简易用户登录(前端+后端+数据库)

目录 前言 一、Cookie 1.1、Cookie从哪里来&#xff1f; 1.2、Cookie到哪里去&#xff1f; 二、Session 2.1、什么是sessionId? 三、Cookie和Session的区别 四、Cookie和Session的具体工作流程 五、代码实现用户登录 5.1、核心方法 5.2、代码 前言 想要了解Cookie和…

【设计模式】桥接模式(Bridge Pattern)

桥接模式属于结构型模式&#xff0c;主要解决因业务增长时滥用继承而导致的**“类爆炸”问题。桥接模式将一个树型的整体继承结构替换为由抽象的类&#xff08;抽象化角色&#xff09;组成的抽象部分和由实现接口&#xff08;实现化角色&#xff09;与实现类&#xff08;具体实…

无法访问mybatis.dto.StudengInVO-使用maven编译报错

一、问题由来 最近一次拉代码后&#xff0c;合并代码然后进行编译时出现一个问题&#xff0c;使用maven在进行编译的时候报一个错&#xff0c;无法访问mybatis.dto.StudengInVO。 突然出现这个错误让自己感觉很奇怪&#xff0c;开发工具已经使用了好几个月&#xff0c;项目也已…

SSM毕设项目 - 基于SSM的汉服文化平台网站(含源码+论文)

文章目录1 项目简介2 实现效果2.1 界面展示3 设计方案3.1 概述3.2 系统流程3.3 系统结构设计4 项目获取1 项目简介 Hi&#xff0c;各位同学好呀&#xff0c;这里是M学姐&#xff01; 今天向大家分享一个今年(2022)最新完成的毕业设计项目作品&#xff0c;【基于SSM的汉服文化…