一、功能简述:
1、编辑文本时可正常弹出键盘
2、选择表情时类似QQ\微信模式,不会因点击输入框聚焦而弹出键盘
3、可支持插入图片类表情
github源码存储地址:emojiView
先上效果(手机打开为最佳效果,PC键盘输入机制和手机不同)
首页 (1019058432.github.io)
其实有的浏览器本身就支持通过API控制键盘是否显示(是的,safari不支持)
VirtualKeyboard API - Web APIs | MDN (mozilla.org)
二、假想方案及问题
1、从文本框编辑离焦再聚焦会导致键盘闪烁,且这时表情列表和键盘同屏(例如打开表情列表后恢复编辑光标)
2、在1的基础上不在聚焦,通过元素模拟光标后计算偏移量(需要处理在中英字符不等宽的情况下计算x,y值,以及超出滚动控制等操作比较复杂易错)
3、若使用div在1的基础上不在聚焦,通过元素模拟光标并内嵌光标,则在文本中移动需要通过点击位置计算光标(x,y)位置,来将光标移动到指定位置,这样不需要计算滚动,但实现过程比较繁琐。
三、解决方案:
1、无需显示光标,在问题1中切换键盘输入/表情列表时输入框不再聚焦。(如微博网页版评论功能)
2、直接使用div模拟input/textarea,配合自定义键盘及点击事件进行控制,缺点是无法使用系统键盘,优点是完全自主控制键盘。
3、使用canvas直接模拟整套编辑dom,难度大,但可定制度会很高。
4、通过div可编辑/不可编辑来配合系统输入法实现。
方案4大体流程:
1、对于光标
可用span标签将文本打散成单个字符并在span上添加标记,并监听div容器点击事件(其实后面发现不用span标签分割也行,利用selectionchange事件中的光标位置考虑各种情况对容器中的html进行拼接就行,也会少很多麻烦。):
1)若点击源头是文本,则在该文本span前插入光标(insertBefore)
2)若点击源头是容器本身,则将光标直接追加到容器末尾(appendChild)
在表情选择时,这时我们就可以模拟APP应用的的键盘和表情列表切换效果(如QQ,Wx)。
首先始终隐藏div原本的光标,使用自定义光标:
caret-color: transparent;
2、对于整体:
1)div可编辑状态,键盘正常弹出,正常编辑
2)选择表情时,将div设为不可编辑,若模拟光标未挂载,则将元素模拟的光标添加到div中,打开渲染表情列表进行表情选择,并将选中表情往元素光标前插入(若插入文本则应该重复用span标签打散再插入)
3)从表情选择面板返回键盘面板,将div设置可编辑状态。
注:div需要通过监听可编辑状态beforeinput事件接管键盘输入
附:
// 文本转dom
function translateElement(str: string) {
const fragment = document.createDocumentFragment()
str.split('').map((char) => {
const charSpan = document.createElement('span')
charSpan.textContent = char
charSpan.className = 'txt'
fragment.appendChild(charSpan)
})
return fragment
}
// 初始化容器输入代理,拦截输入进行包装
const beforeInputHandle = useCallback(event => {
const inputTextWord = event.data || "";
textareaChangeHandle(inputTextWord); // 此处为模拟onChange()事件
// 因需要包装为元素,禁止直接输入到div中
event.preventDefault();
return false;
}, []);
useEffect(() => {
if (divRef.current) {
divRef.current.addEventListener("beforeinput", beforeInputHandle);
return () => {
divRef.current?.removeEventListener("beforeinput", beforeInputHandle);
};
}
}, []);