React Input với các từ khóa nổi bật

Mar 10 2023
Làm thế nào một nhiệm vụ được cho là đơn giản biến thành một vấn đề đau đầu
Điều đầu tiên xuất hiện trong đầu tôi là ai đó có thể đã xây dựng một thành phần như vậy và điều duy nhất tôi phải làm là điều chỉnh giải pháp đã triển khai. Sau một nghiên cứu ngắn, tôi đã tìm thấy một ví dụ được tạo bằng cách sử dụng vùng văn bản hoạt động chính xác như vùng văn bản với khả năng đánh dấu các từ riêng biệt.
Bản demo của kết quả cuối cùng

Điều đầu tiên xuất hiện trong đầu tôi là ai đó có thể đã xây dựng một thành phần như vậy và điều duy nhất tôi phải làm là điều chỉnh giải pháp đã triển khai. Sau một nghiên cứu ngắn, tôi đã tìm thấy một ví dụ được tạo bằng cách sử dụng vùng văn bản hoạt động chính xác như vùng văn bản với khả năng đánh dấu các từ riêng biệt.
Ngoài ra, các câu trả lời trên StackOverflow đã làm rõ rằng đây không phải là một nhiệm vụ đơn giản.

Một giải pháp rõ ràng là làm cho thành phần này gồm hai phần: thẻ nhập văn bản ở phía trước với văn bản trong suốt và dấu mũ màu và div được định vị tuyệt đối ở phía sau, chứa văn bản được tạo kiểu có thể nhìn thấy. Bằng cách này, tôi đã có văn bản hoàn toàn có thể tùy chỉnh.

Tôi sẽ loại bỏ các kiểu CSS ra khỏi bài viết này vì nó sẽ chiếm quá nhiều dung lượng. Nguyên tắc cốt lõi rất đơn giản — thuộc tính phông chữ, phần đệm và kích thước phải giống hệt nhau cho cả đầu vào và div ở mặt sau.

const [value, setValue] = useState('');

const onChange = (e) => setValue(e.target.value);
const format = (text) => // formatted jsx
<div className="container">
  <div className="text">{format(value)}</div>
  <input type="text" className="field" value={value} onChange={onChange} />
</div>

Khi nhập vào chuỗi đầu vào quá dài, trường bắt đầu cuộn nhưng văn bản trong div vẫn tĩnh.

Sau những cuộc thảo luận dài với đồng nghiệp của tôi, chúng tôi nhận ra rằng chúng tôi có thể lấy thuộc tính scroll left của đầu vào và đặt nó cho phần tử div .

// ...
const textBlockRef = useRef(null);
const [scrollValue, setScrollValue] = useState(0);
// ...
const handleScroll = (e) => setScrollValue(e.currentTarget.scrollLeft);

useEffect(() => {
  if (textBlockRef.current) {
    textBlockRef.current.scrollLeft = scrollValue;
  }
}, [scrollValue]);
<!-- ... -->
<div className="text" ref={textBlockRef}>{format(value)}</div>
<input
  type="text"
  className="field"
  value={value}
  onChange={onChange}
  onScroll={handleScroll}
 />
<!-- ... -->

Vấn đề thứ hai

Hóa ra tôi vẫn chưa giải quyết triệt để vấn đề đầu tiên và đó là vấn đề thứ hai.

Safari không hỗ trợ thuộc tính onScroll của phần tử đầu vào.

Chà, nó thay đổi trò chơi (tôi nghĩ vậy), và tôi thực sự bắt đầu đặt câu hỏi về cách tiếp cận của mình. Nhưng cảm ơn Chúa, tôi phát hiện ra rằng Safari hỗ trợ thuộc tính onScroll của phần tử vùng văn bản .
Điều đó đã giải quyết được vấn đề.
Tất cả những gì tôi phải làm là cách điệu vùng văn bản để nó trông giống như một đầu vào[type=”text”] và thế là xong.

<!-- ... -->
<textarea
  className="field"
  ref={ref}
  value={value}
  onChange={onChange}
  onScroll={handleScroll}
  rows={1}
/>
<!-- ... -->

Khi nhấn phím Enter textarea tạo một dòng mới. Thật dễ dàng để xử lý bằng cách tạo trình xử lý onKeyDown và hủy hành vi Nhập mặc định. Ngoài ra, nếu trường được bao bọc bởi thẻ biểu mẫu thì cũng cần phải gửi yêu cầu.

// ...
  const submitHandler = (e) => {
    if (e.keyCode === 13 || e.key === "Enter" || e.which === 13) {
      if (!e.repeat && e.target.form) {
        e.target.form.submit();
      }
      e.preventDefault();
    }
  };
// ...
<!-- ... -->
<textarea
  className="field"
  ref={ref}
  value={value}
  onChange={onChange}
  onScroll={handleScroll}
  onKeyDown={submitHandler}
  rows={1}
/>
<!-- ... -->

Có thể, nhiều vấn đề hơn sẽ được tìm thấy trong giải pháp này và tôi sẽ rất vui nếu bạn chia sẻ những gì bạn tìm thấy trong phần nhận xét.