html - Chrome/Android vs Safari/iOS 上的 textarea 行为
问题描述
我正在开发一个 Angular 8 webapp 项目,供多个平台(iOS、Android、Windows 等)使用。我注意到使用 Safari 和 Chrome 在外观上存在一些差异。例如,当ngIf
在 a<div>
中放置一个错误条件时,当它变为真时它不会显示在 Safari 的视图中,但会显示在 Chrome 中(将 放置ngIf
在<ng-container>
包装器中可以解决此问题)。
我当前的问题是关于使用<textarea>
. 在 Safari 中使用以下内容textarea
时,会不断出现退格问题。我能够编写第一行文本,并且在回车之前,退格按预期工作(立即删除光标左侧的单个字符)。回车后,如果使用了退格,则光标之前的所有字符都将被删除,而光标之后的所有字符(如果有)则保留。当光标位于第一行以外的任何行上的任何位置时,就会发生这种行为,我仍然可以按预期使用退格键。下面的第一个屏幕截图显示了在我将光标放在第二行末尾之前每行包含的内容。第二个屏幕截图显示了按下退格键后会发生什么。
使用 Chrome、Firefox 或 Edge 时未观察到此行为,而是按预期运行。我在 Macbook Pro Mojave 上运行,并在发布时使用了最新的浏览器版本。对于开发人员和用户(不仅限于我的设置),这种行为在本地和生产中都会发生。
关于导致这种行为的原因以及如何减轻它的任何想法?
这是正在使用的代码(由于专有问题,我只共享引用的元素/函数,而不是整个文件):
从子 HTML
<textarea class="textbox form-control hammerContainer" id="{{id}}" name="textArea" placeholder="{{placeholder}}" rows="{{rowNumbers}}"
#textArea value="{{value}}" [attr.minLength]="minlength" [attr.maxLength]="maxlength" (keyup)="writeValue($event.target.value)"
(blur)="onTouched()" (paste)="onPaste($event)" (click)="getCursorPos(textArea)" (keydown)="getCursorPos(textArea)" (contextmenu)="getCursorPos(textArea)"
(select)="getCursorPos(textArea)" (press)="getCursorPos(textArea)" (tap)="getCursorPos(textArea)">
</textarea>
来自子 .ts
import { Component, OnInit, Output [...more imports...], EventEmitter, ChangeDetectorRef } from '@angular/core';
@Output() valueChange = new EventEmitter<string>();
private caretPosStart: number = 0;
private caretPosEnd: number = 0;
public onChange: Function = (val: string) => { };
public onTouched: Function = () => { };
constructor(private changeDetectorRef: ChangeDetectorRef) { }
public writeValue(value: any): void {
if (value !== null && value !== undefined) {
this.value = value.substring(0,this.maxlength);
this.onChange(this.value);
this.valueChange.emit(this.value);
}
}
public getCursorPos(element: any): void {
if (element.selectionEnd || element.selectionEnd === 0) {
this.caretPosStart = element.selectionStart;
this.caretPosEnd = element.selectionEnd;
}
}
onPaste(event: ClipboardEvent): void {
try {
event.preventDefault(); // Prevent default action in order to not duplicate pasted value
const clipboardData = event ? event.clipboardData : undefined;
let pastedText = clipboardData ? clipboardData.getData('text') : '';
if (pastedText) {
const selectedText = window.getSelection().toString(); // Get selected text, if any
// If selectedText has data, then this.value should exist with data as well, hence why there is no additional checks for
// this.value before setting currentTextArr
if (selectedText) {
// Split selectedText and this.value into arrays in order to compare the string values.
// If any string values match, and based on caret position in case of multiples of same word(s), filter/remove them
const selectedTextArr = selectedText.split(' ');
const currentTextArr = this.value.split(' ');
let firstMatchIndex;
let currentStrCount = 0;
for (let x = 0; x < currentTextArr.length; x++) {
currentStrCount += (currentTextArr[x].length + 1);
for (let i = 0; i < selectedTextArr.length; i++) {
if (currentTextArr[x] === selectedTextArr[i] && ((this.caretPosStart < currentStrCount) && ((currentStrCount - 2) <= this.caretPosEnd))) {
if (!firstMatchIndex) {
firstMatchIndex = x; // setting index based on the first word match to know where to insert pasted text
}
currentTextArr.splice(x, 1);
}
}
}
// If there was a match, insert the pasted text based on the index of the first matched word, otherwise the pasted text will be placed at the end
// of the current data. Then format the array back into a string and write the value.
let finalText;
if (firstMatchIndex) {
currentTextArr.splice(firstMatchIndex, 0, pastedText);
finalText = currentTextArr.join(' ');
} else {
finalText = currentTextArr.join(' ') + ' ' + pastedText;
}
this.writeValue(finalText);
this.changeDetectorRef.detectChanges();
// Update caret position after paste
this.caretPosStart = finalText.length;
this.caretPosEnd = finalText.length;
} else {
// Check to see if there is existing data
if (this.value) {
// If the carotPos is less than the current strings length, add the
// pasted text where the cursor is
if (this.caretPosStart < this.value.length) {
pastedText = this.value.slice(0, this.caretPosStart) + pastedText + this.value.slice(this.caretPosStart);
} else { // Otherwise add pasted text after current data
pastedText = this.value + ' ' + pastedText;
}
}
this.writeValue(pastedText);
this.changeDetectorRef.detectChanges();
// Update caret position after paste
this.caretPosStart = pastedText.length;
this.caretPosEnd = pastedText.length;
}
}
} catch (e) {
// Do nothing if error occurs. This just prevents the app from breaking if there is an issue handling the pasting of data.
// However, will still be able to enter additional narrative text manually.
}
}
从父 HTML
<app-textarea (valueChange)='setSpecialInstructions($event)'>
</app-textarea>
来自父 .ts
setSpecialInstructions(value: string) {
this.specialInstructions = value;
this.someService.setSpecialInstructions(this.specialInstructions);
}
解决方案
我忘记添加的一件事是包含<textarea>
标签的子组件的 CSS 文件。锤子容器类如下:
.hammerContainer {
user-select: all !important;
}
user-select
是我以前从未遇到过的 CSS 样式(这段代码是别人写的)。经过一番研究,我发现在 Safari 中控制该设置时使用的适当样式-webkit-user-select
如下:
.hammer-container {
user-select: all !important;
-webkit-user-select: text !important;
}
user-select
使用该all
设置时,Safari 不支持该设置,因此需要text
在-webkit-user-select
. 确保覆盖的!important
默认值none
。
推荐阅读
- python-3.x - 如何在 Python 中平均顶部或底部 'n' 值
- android - getString() 总是返回默认的语言字符串。如何检查应用程序是否正在寻找本地化的字符串文件?
- python - 在复杂语句上使用列表推导的最佳方法
- jquery - jquery load() 后提交不工作
- java - 尽管明显成功,Glassfish/JAX-RS 仍返回 404
- javascript - 为什么 interate 函数首先打印而不是 randQuestion 函数打印?
- python-2.7 - 输入错误密码时尝试弹出弹出窗口 Kivy
- java - FCM 数据消息无法正常工作
- python - 当每个视图需要处理不同的模型时,如何遵循django中的DRY原则?
- javascript - 将外部网站嵌入页面并与内容交互