Sự phức tạp của thanh cuộn vô hạn

Tóm tắt: Sử dụng lại các phần tử DOM của bạn và xoá các phần tử cách xa khung nhìn. Sử dụng phần giữ chỗ để tính đến dữ liệu bị trễ. Sau đây là bản minh hoạ cho đoạn mã trình cuộn.

Trình cuộn vô hạn bật lên trên khắp Internet. Danh sách nghệ sĩ của Google Music là một là dòng thời gian của Facebook là một và trang video trực tiếp của Twitter cũng vậy. Bạn cuộn xuống và trước khi bạn cuộn đến cuối trang, nội dung mới sẽ xuất hiện một cách kỳ diệu dường như bất ngờ. Đây là một trải nghiệm liền mạch cho người dùng và dễ dàng xem đơn khiếu nại.

Tuy nhiên, thử thách kỹ thuật đằng sau một công cụ cuộn vô hạn khó hơn . Phạm vi vấn đề mà bạn gặp phải khi muốn thực hiện The Right ThingTM là rất lớn. Ứng dụng bắt đầu từ những thứ đơn giản như các đường liên kết ở chân trang thực tế không thể tiếp cận được vì nội dung liên tục đẩy chân trang ra xa. Tuy nhiên, trở nên khó khăn hơn. Cách xử lý sự kiện đổi kích thước khi người dùng thay đổi kích thước điện thoại từ dọc sang ngang hay làm cách nào để điện thoại không bị kim loại dừng lại một cách khó khăn khi danh sách quá dài?

Điều đúng đắnTM

Chúng tôi nghĩ thế là đủ lý do để đưa ra cách triển khai tệp tham chiếu chỉ ra cách giải quyết tất cả vấn đề này theo cách có thể tái sử dụng trong khi duy trì các tiêu chuẩn hiệu suất.

Chúng tôi sẽ sử dụng 3 kỹ thuật để đạt được mục tiêu: tái chế DOM, bia mộ và cuộn neo.

Trường hợp minh hoạ sẽ là một cửa sổ trò chuyện giống như Hangouts, nơi chúng ta có thể cuộn thông qua các tin nhắn. Điều đầu tiên chúng ta cần là một nguồn trò chuyện vô tận tin nhắn. Về mặt kỹ thuật, không có những người cuộn vô hạn nào thực sự vô hạn, nhưng với lượng dữ liệu có sẵn để bơm vào mà họ cũng có thể cuộn. Để đơn giản, chúng ta sẽ chỉ mã hoá cứng một tập hợp các tin nhắn trò chuyện và chọn tin nhắn, tác giả và đôi khi đính kèm hình ảnh tại ngẫu nhiên với một loạt độ trễ giả tạo để hoạt động giống như mạng thực.

Ảnh chụp màn hình ứng dụng Chat

Tái chế DOM

Tái chế DOM là một kỹ thuật chưa được khai thác đúng mức để giữ số lượng nút DOM ở mức thấp. Chiến lược phát hành đĩa đơn ý tưởng chung là sử dụng các phần tử DOM đã tạo nằm ngoài màn hình tạo quảng cáo mới. Phải thừa nhận rằng bản thân các nút DOM có giá rẻ, nhưng chúng không miễn phí, vì mỗi yếu tố trong số này đều làm tăng thêm chi phí bộ nhớ, bố cục, kiểu và độ vẽ. Các thiết bị cấp thấp sẽ hoạt động chậm hơn đáng kể nếu không hoàn toàn không sử dụng được nếu trang web có quá lớn DOM không thể quản lý. Ngoài ra, xin lưu ý rằng mọi bố cục lại và áp dụng lại các kiểu của bạn – một quá trình được kích hoạt bất cứ khi nào một lớp được thêm vào hoặc bị xoá khỏi một nút – sẽ tốn kém hơn nếu có DOM lớn hơn. Việc tái chế các nút DOM có nghĩa là chúng tôi sẽ giữ lại tổng số DOM các nút thấp hơn đáng kể, giúp tất cả các quy trình này nhanh hơn.

Rào cản đầu tiên là chính việc cuộn. Vì chúng ta chỉ có một tập hợp con nhỏ của tất cả các mục có sẵn trong DOM tại bất kỳ thời điểm nào, chúng ta cần tìm một cách khác giúp thanh cuộn của trình duyệt phản ánh chính xác lượng nội dung về mặt lý thuyết. Chúng ta sẽ sử dụng phần tử sentinel 1px x 1px với hiệu ứng biến đổi để buộc phần tử chứa các mục – đường băng – có kết quả mong muốn chiều cao. Chúng tôi sẽ quảng bá mọi yếu tố trên đường băng lên sàn diễn thời trang của họ để tạo nên đảm bảo rằng lớp đường băng hoàn toàn trống rỗng. Không có màu nền, không có gì. Nếu lớp trên đường băng không phải là lớp trống, nó sẽ không đủ điều kiện cho và chúng tôi sẽ phải lưu trữ hoạ tiết trên thẻ đồ hoạ chiều cao vài trăm nghìn pixel. Chắc chắn không khả thi trên thiết bị di động của bạn.

Bất cứ khi nào cuộn, chúng tôi sẽ kiểm tra xem khung nhìn đã đủ gần với ở cuối đường băng. Nếu có, chúng tôi sẽ mở rộng đường băng bằng cách di chuyển người giám sát và di chuyển các mục đã rời khỏi khung nhìn xuống cuối và đưa nội dung mới vào đó.

Đường băng Sentinel Khung nhìn

Tương tự như vậy khi cuộn theo hướng khác. Tuy nhiên, chúng tôi sẽ không bao giờ thu nhỏ đường băng trong quá trình triển khai để vị trí thanh cuộn vẫn nằm nhất quán.

Tombstone

Như đã đề cập trước đó, chúng tôi cố gắng làm cho nguồn dữ liệu hoạt động giống như thế giới thực. Tận hưởng độ trễ mạng và mọi tính năng khác. Điều đó có nghĩa là nếu người dùng sử dụng tính năng cuộn linh hoạt, họ có thể dễ dàng cuộn qua phần tử cuối cùng mà chúng tôi có dữ liệu. Nếu điều đó xảy ra, chúng tôi sẽ đặt một vật phẩm là bia mộ – một phần giữ chỗ – sẽ được thay thế bằng mục có nội dung thực tế sau khi đã có dữ liệu. Những bia mộ này cũng được tái chế và có bể bơi riêng để các phần tử DOM có thể sử dụng lại. Chúng tôi cần điều đó để có thể thực hiện chuyển đổi tốt đẹp từ tombstone cho mục được điền sẵn nội dung mà nếu không thì sẽ rất khó gây khó chịu cho người dùng và thực sự có thể khiến họ mất dấu tập trung vào.

Chẳng hạn
. Rất đá. Tuyệt vời.

Một thử thách thú vị ở đây là các mục thực có thể có chiều cao lớn hơn vì số lượng văn bản khác nhau trên mỗi mục hoặc tệp đính kèm hình ảnh. Để giải quyết vấn đề này, chúng tôi sẽ điều chỉnh vị trí cuộn hiện tại mỗi khi dữ liệu được đưa vào và một tombstone sẽ được thay thế phía trên khung nhìn, cố định vị trí cuộn đến một phần tử thay vì giá trị pixel. Khái niệm này có tên là cuộn neo.

Neo cuộn

Tính năng cuộn neo của chúng tôi sẽ được gọi cả khi tombstone được thay thế dưới dạng cũng như khi cửa sổ được đổi kích thước (điều này cũng xảy ra khi thiết bị bị lật ngược!). Chúng ta sẽ phải tìm ra phần tử nào xuất hiện nhiều nhất trong khung nhìn. Vì phần tử đó chỉ có thể hiển thị một phần, nên chúng tôi cũng sẽ lưu trữ độ lệch từ đầu phần tử nơi khung nhìn bắt đầu.

Sơ đồ neo cuộn.

Nếu khung nhìn được đổi kích thước và đường băng có những thay đổi, chúng ta có thể khôi phục lại trong một tình huống có vẻ giống với người dùng. Chiến thắng! Ngoại trừ ảnh đã đổi kích thước có nghĩa là mỗi mục có khả năng thay đổi chiều cao, vậy làm cách nào để chúng ta bạn biết nội dung cố định nên được đặt xuống bao xa? Chúng tôi không có! Để tìm hiểu chúng tôi sẽ phải bố trí mọi phần tử phía trên mục cố định và cộng lại tất cả tầm cao của mình; điều này có thể gây ra một khoảng thời gian tạm dừng đáng kể sau khi đổi kích thước, đồng thời chúng tôi cũng muốn điều đó. Thay vào đó, chúng ta sử dụng giả định rằng mọi mục ở trên đều có cùng kích thước làm bia mộ và điều chỉnh vị trí cuộn sao cho phù hợp. Khi các phần tử là khi cuộn vào đường băng, chúng tôi điều chỉnh vị trí cuộn, giúp trì hoãn hiệu quả bố cục hoạt động đến khi thực sự cần thiết.

Bố cục

Tôi đã bỏ qua một chi tiết quan trọng: Bố cục. Mỗi lần tái chế một phần tử DOM thường sẽ bố trí lại toàn bộ đường băng, điều này sẽ đưa chúng tôi xuống dưới mức mục tiêu là 60 khung hình/giây. Để tránh tình trạng này, chúng tôi đang gánh nặng bố cục cho chính mình và sử dụng các phần tử có vị trí tuyệt đối có biến đổi. Bằng cách này, chúng tôi có thể giả định rằng tất cả các thành phần ở phía trên đường băng vẫn chiếm không gian khi thực tế chỉ có không gian trống. Vì chúng tôi đang thực hiện chúng tôi có thể lưu vào bộ nhớ đệm các vị trí nơi mỗi mục kết thúc và chúng tôi có thể tải ngay phần tử chính xác từ bộ nhớ đệm khi người dùng cuộn ngược.

Lý tưởng nhất là các vật phẩm chỉ được sơn lại một lần khi được gắn vào DOM và không bị mờ ám khi thêm hoặc loại bỏ các vật phẩm khác trên đường băng. Tức là khả thi, nhưng chỉ với các trình duyệt hiện đại.

Điều chỉnh chi tiết

Gần đây, Chrome đã hỗ trợ thêm cho Vùng chứa CSS, một tính năng giúp các nhà phát triển cho trình duyệt biết rằng một phần tử là ranh giới đối với bố cục và tô màu. Vì chúng tôi đang tự bố trí ở đây, nên đó là để kiểm soát. Bất cứ khi nào thêm một phần tử vào sàn diễn, chúng ta đều biết các mục khác không cần chịu ảnh hưởng của việc bố cục lại. Mỗi mục nhận contain: layout. Chúng tôi cũng không muốn ảnh hưởng đến phần còn lại của trang web, do đó, đường băng cũng sẽ nhận được lệnh kiểu này.

Một điều khác chúng tôi cân nhắc là sử dụng IntersectionObservers làm cơ chế phát hiện thời điểm người dùng đã cuộn đủ xa để chúng tôi bắt đầu tái chế các phần tử và tải phần tử mới . Tuy nhiên, IntersectionObservers được chỉ định là có độ trễ cao (như thể sử dụng requestIdleCallback), nên chúng ta có thể thực sự cảm thấy phản hồi kém hơn với IntersectionObservers so với khi không có. Ngay cả khi triển khai hiện tại bằng Sự kiện scroll gặp phải vấn đề này, vì các sự kiện cuộn được gửi đi trên một cơ sở "nỗ lực tối đa". Cuối cùng, worklet tổng hợp của Houdini sẽ là giải pháp có độ chân thực cao cho vấn đề này.

Vẫn chưa hoàn hảo

Triển khai tái chế DOM hiện tại của chúng tôi không lý tưởng vì nó bổ sung tất cả các phần tử đi qua khung nhìn, thay vì chỉ quan tâm đến những khung nhìn thực sự trên màn hình. Điều này có nghĩa là khi bạn cuộn thực sự nhanh, bạn đặt có quá nhiều công việc đối với bố cục và tô màu trên Chrome đến mức trình duyệt không thể cập nhật. Bạn sẽ kết thúc không thấy gì ngoài nền. Đó không phải là ngày tận thế mà là chắc chắn là cần phải cải thiện.

Chúng tôi hy vọng bạn thấy những bài toán đơn giản có thể trở nên khó khăn như thế nào khi bạn muốn kết hợp trải nghiệm người dùng tuyệt vời với tiêu chuẩn hiệu suất cao. Bằng Ứng dụng web tiến bộ sẽ trở thành trải nghiệm cốt lõi trên điện thoại di động, điều này sẽ trở nên quan trọng hơn và các nhà phát triển web sẽ phải tiếp tục đầu tư vào bằng cách sử dụng các mẫu tôn trọng hạn chế về hiệu suất.

Bạn có thể tìm thấy tất cả các đoạn mã này trong kho lưu trữ của chúng tôi. Chúng tôi đã thực hiện tốt nhất là giữ cho nó có thể tái sử dụng, nhưng sẽ không xuất bản dưới dạng thư viện thực tế npm hoặc dưới dạng một kho lưu trữ riêng. Mục đích sử dụng chính là giáo dục.