我正在开发一个Angular 8 webapp项目,以供多个平台(iOS,Android,Windows等)使用。我注意到使用Safari和Chrome外观上有一些差异。例如,将ngIf
置于错误条件下的<div>
内时,它变为true时将不会显示在Safari的视图中,但会显示在Chrome中(将ngIf
放置在<ng-container>
中]包装器可以解决此问题)。
我当前的问题是关于使用<textarea>
。在Safari中使用以下textarea
时,经常出现退格问题。我能够编写文本的第一行,并且在回车之前,退格键可以按预期工作(立即删除光标左侧的单个字符)。回车后,如果使用了退格键,则删除光标之前的所有字符,而光标之后的所有字符(如果有)将保留。当光标位于第一行以外的任何行上的任何位置时,会出现此现象,在这里我仍然可以按预期使用退格键。下面的第一个屏幕截图显示了在将光标放在第二行的末尾之前每一行包含的内容。第二张屏幕截图显示了当我按下Backspace按钮时会发生什么。
使用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类如下:
.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
的默认值。