Giao diện của Trung Địa

Hướng dẫn từng bước về quá trình phát triển cho nhiều thiết bị

Daniel Isaksson
Daniel Isaksson
Einar Öberg
Einar Öberg

Trong bài viết đầu tiên của chúng tôi về sự phát triển của Thử nghiệm Chrome Hành trình qua Trung địa, chúng tôi tập trung vào việc phát triển WebGL cho thiết bị di động. Trong bài viết này, chúng ta thảo luận về những thách thức, vấn đề và giải pháp mà chúng tôi gặp phải khi tạo phần còn lại của giao diện người dùng HTML5.

Ba phiên bản của cùng một trang web

Trước tiên, hãy nói một chút về việc điều chỉnh thử nghiệm này để hoạt động trên cả máy tính và thiết bị di động từ góc độ kích thước màn hình và khả năng của thiết bị.

Toàn bộ dự án theo phong cách rất “điện ảnh”, trong đó chúng tôi thiết kế khôn ngoan để giữ trải nghiệm trong khung hình cố định hướng ngang để giữ được sự kỳ diệu của bộ phim. Vì một phần lớn của dự án bao gồm các "trò chơi" nhỏ tương tác, nên việc để chúng tràn khung hình cũng sẽ không hợp lý.

Chúng tôi có thể lấy trang đích làm ví dụ về cách chúng tôi điều chỉnh thiết kế cho phù hợp với các kích thước khác nhau.

Đại bàng vừa thả chúng tôi xuống trang đích.
Đại bàng vừa thả chúng tôi vào trang đích.

Trang web có 3 chế độ khác nhau: máy tính để bàn, máy tính bảng và thiết bị di động. Không chỉ xử lý bố cục, mà còn vì chúng ta cần xử lý các thành phần được tải trong thời gian chạy và thêm nhiều tính năng tối ưu hoá hiệu suất. Với các thiết bị có độ phân giải cao hơn máy tính để bàn và máy tính xách tay nhưng lại có hiệu suất kém hơn điện thoại, việc xác định bộ quy tắc tối ưu không phải là một tác vụ dễ dàng.

Chúng tôi sử dụng dữ liệu tác nhân người dùng để phát hiện thiết bị di động và kiểm tra kích thước khung nhìn để nhắm mục tiêu đến máy tính bảng (từ 645px trở lên). Trên thực tế, mỗi chế độ khác nhau có thể hiển thị tất cả độ phân giải, vì bố cục dựa trên truy vấn phương tiện hoặc vị trí tương đối/phần trăm với JavaScript.

Vì các thiết kế trong trường hợp này không dựa trên lưới hoặc quy tắc và khá khác biệt giữa các phần khác nhau, nên thực sự phụ thuộc vào phần tử và trường hợp cụ thể như điểm ngắt hoặc kiểu sẽ sử dụng. Điều này xảy ra nhiều lần khi chúng ta thiết lập bố cục hoàn hảo với các công cụ sass-mixin và truy vấn phương tiện truyền thông đẹp mắt. Sau đó, chúng tôi cần thêm hiệu ứng dựa trên vị trí chuột hoặc đối tượng động và kết thúc bằng việc viết lại mọi thứ trong JavaScript.

Chúng ta cũng thêm một lớp có chế độ hiện tại trong thẻ tiêu đề để có thể sử dụng thông tin đó theo các kiểu, như trong ví dụ sau (trong SCSS):

.loc-hobbit-logo {

  // Default values here.

  .desktop & {
     // Applies only in desktop mode.
  }

 .tablet &, .mobile & {
   
   // Different asset for mobile and tablets perhaps.

   @media screen and (max-height: 760px), (max-width: 760px) {
     // Breakpoint-specific styles.
   }

   @media screen and (max-height: 570px), (max-width: 400px) {
     // Breakpoint-specific styles.
   }
 }
}

Chúng tôi hỗ trợ tất cả các kích thước xuống còn khoảng 360x320. Đây quả là một khó khăn khi tạo trải nghiệm web sống động. Trên máy tính để bàn, chúng tôi có kích thước tối thiểu trước khi hiển thị thanh cuộn vì chúng tôi muốn bạn trải nghiệm trang web trong khung nhìn lớn hơn nếu có thể. Trên thiết bị di động, chúng tôi quyết định cho phép cả chế độ ngang và chế độ dọc cho đến trải nghiệm tương tác, trong đó chúng tôi yêu cầu bạn xoay thiết bị sang chế độ ngang. Lý do phản đối đó là trang web không hiển thị theo chiều dọc như ở chế độ ngang; nhưng trang web được điều chỉnh theo tỷ lệ khá tốt nên chúng tôi đã giữ nguyên tỷ lệ đó.

Điều quan trọng cần lưu ý là bố cục không nên kết hợp với phát hiện tính năng như loại dữ liệu đầu vào, hướng thiết bị, cảm biến, v.v. Những tính năng đó có thể tồn tại ở tất cả các chế độ này và cần trải rộng trên tất cả các chế độ. Một ví dụ là tính năng hỗ trợ chuột và chạm cùng lúc. Bù võng mạc cho chất lượng nhưng hầu hết hiệu suất lại là một vấn đề khác, đôi khi chất lượng thấp hơn lại tốt hơn. Ví dụ: canvas có độ phân giải một nửa trong các trải nghiệm WebGL trên màn hình retina, nếu không thì canvas sẽ phải hiển thị số lượng pixel gấp bốn lần

Chúng tôi thường xuyên sử dụng công cụ trình mô phỏng trong Công cụ cho nhà phát triển trong quá trình phát triển, đặc biệt là trong Chrome Canary với các tính năng mới được cải tiến và nhiều giá trị đặt trước. Đây là một cách hay để nhanh chóng xác thực thiết kế. Chúng tôi vẫn cần thường xuyên kiểm thử trên thiết bị thực. Một lý do là vì trang web đang điều chỉnh sang chế độ toàn màn hình. Các trang có thao tác cuộn dọc sẽ ẩn giao diện người dùng của trình duyệt khi cuộn trong hầu hết các trường hợp (Safari trên iOS7 hiện đang gặp vấn đề) nhưng chúng tôi phải điều chỉnh mọi thứ độc lập với nhau. Chúng tôi cũng sử dụng một giá trị đặt trước trong trình mô phỏng và thay đổi chế độ cài đặt kích thước màn hình để mô phỏng việc mất không gian có sẵn. Việc kiểm thử trên thiết bị thực cũng rất quan trọng để theo dõi mức sử dụng bộ nhớ và hiệu suất

Xử lý trạng thái

Sau trang đích, chúng ta sẽ truy cập vào bản đồ Trung địa. Bạn có thấy URL thay đổi không? Trang web này là một ứng dụng trang đơn sử dụng API lịch sử để xử lý việc định tuyến.

Mỗi phần của trang web là đối tượng riêng kế thừa nguyên mẫu chức năng như phần tử DOM, chuyển đổi, tải tài sản, loại bỏ, v.v. Khi bạn khám phá các phần khác nhau của trang web, các phần sẽ được tải, các phần tử được thêm vào và xóa khỏi DOM và nội dung cho phần hiện tại.

Vì người dùng có thể nhấn vào nút quay lại của trình duyệt hoặc điều hướng qua trình đơn bất kỳ lúc nào nên mọi thứ được tạo cần phải được vứt bỏ tại một thời điểm nào đó. Thời gian chờ và ảnh động cần phải được dừng và loại bỏ, nếu không sẽ gây ra hành vi, lỗi và rò rỉ bộ nhớ không mong muốn. Đây không phải là việc dễ dàng, đặc biệt là khi thời hạn đang đến gần và bạn cần hoàn thành mọi việc càng nhanh càng tốt.

Giới thiệu các vị trí

Để thể hiện các bối cảnh đẹp mắt và các nhân vật của Trung địa, chúng tôi đã xây dựng một hệ thống theo mô-đun gồm các thành phần hình ảnh và văn bản. Bạn có thể kéo hoặc vuốt theo chiều ngang. Chúng tôi chưa bật thanh cuộn ở đây vì muốn có các tốc độ khác nhau trên nhiều phạm vi, chẳng hạn như trong chuỗi hình ảnh mà bạn dừng chuyển động sang một bên cho đến khi đoạn video phát xong.

Lâu đài của Thranduil
Dòng thời gian của Thranduil's Hall

Tiến trình

Khi quá trình phát triển bắt đầu, chúng tôi đã không biết nội dung của các mô-đun cho từng vị trí. Điều chúng tôi biết là chúng tôi muốn có một cách mẫu để hiển thị các loại phương tiện truyền thông và thông tin khác nhau theo tiến trình ngang sẽ giúp chúng tôi tự do có 6 bài trình bày địa điểm khác nhau mà không phải xây dựng lại mọi thứ sáu lần. Để quản lý việc này, chúng tôi đã tạo một trình kiểm soát dòng thời gian xử lý việc kéo các mô-đun dựa trên các chế độ cài đặt và hành vi của mô-đun.

Mô-đun và thành phần hành vi

Các mô-đun khác mà chúng tôi đã hỗ trợ gồm có chuỗi hình ảnh, hình ảnh tĩnh, cảnh thị sai, cảnh và văn bản chuyển tiêu điểm.

Mô-đun cảnh thị sai có nền mờ với số lượng lớp tùy chỉnh theo dõi tiến trình khung nhìn để biết các vị trí chính xác.

Cảnh dịch chuyển tiêu điểm là một biến thể của nhóm thị sai, với bổ sung là chúng tôi sử dụng 2 hình ảnh cho mỗi lớp mờ dần để mô phỏng sự thay đổi tiêu điểm. Chúng ta đã thử sử dụng bộ lọc làm mờ nhưng bộ lọc này vẫn tốn kém, vì vậy chúng ta sẽ đợi chương trình đổ bóng CSS làm việc này.

Nội dung trong mô-đun văn bản được kích hoạt bằng cách kéo bằng trình bổ trợ TweenMax Kéo được. Bạn cũng có thể dùng con lăn chuột hoặc vuốt bằng hai ngón tay để cuộn theo chiều dọc. Hãy lưu ý rằng throw-props-plugin sẽ bổ sung vật lý theo kiểu hất khi bạn vuốt và thả ra.

Các mô-đun cũng có thể có các hành vi khác nhau được thêm dưới dạng một nhóm thành phần. Tất cả các chiến dịch này đều có bộ chọn và chế độ cài đặt mục tiêu riêng. Dịch để di chuyển một phần tử, điều chỉnh tỷ lệ để thu phóng, điểm phát sóng cho lớp phủ thông tin, chỉ số gỡ lỗi để kiểm tra bằng hình ảnh, lớp phủ tiêu đề bắt đầu, lớp loé sáng và một số thành phần khác. Các thành phần này sẽ được thêm vào DOM hoặc kiểm soát phần tử mục tiêu của chúng bên trong mô-đun.

Với vị trí này, chúng ta có thể tạo nhiều vị trí khác nhau chỉ bằng một tệp cấu hình giúp xác định những tài sản cần tải cũng như thiết lập các loại mô-đun và thành phần khác nhau.

Chuỗi hình ảnh

Khó khăn nhất trong các mô-đun về khía cạnh hiệu suất và kích thước tải xuống là trình tự hình ảnh. Có rất nhiều thông tin để đọc về chủ đề này. Trên thiết bị di động và máy tính bảng, chúng tôi thay thế hình ảnh này bằng hình ảnh tĩnh. Có quá nhiều dữ liệu cần giải mã và lưu trữ trong bộ nhớ nếu chúng ta muốn có chất lượng tốt trên thiết bị di động. Chúng tôi đã thử nhiều giải pháp thay thế; sử dụng hình nền và sprite trước, nhưng điều này dẫn đến các vấn đề về bộ nhớ và độ trễ khi GPU cần hoán đổi giữa các sprite. Sau đó, chúng tôi đã thử hoán đổi các phần tử img nhưng tốc độ cũng quá chậm. Việc vẽ một khung từ hình sprite thành canvas mang lại hiệu quả cao nhất, vì vậy chúng tôi đã bắt đầu tối ưu hoá việc đó. Để tiết kiệm thời gian tính toán cho mỗi khung, dữ liệu hình ảnh cần ghi vào canvas được xử lý trước thông qua một canvas tạm thời và được lưu bằng PutImageData() vào một mảng, được giải mã và sẵn sàng để sử dụng. Sau đó, spritesheet ban đầu có thể được thu thập rác và chúng tôi chỉ lưu trữ lượng dữ liệu tối thiểu cần thiết trong bộ nhớ. Có thể việc lưu trữ các hình ảnh chưa giải mã thực ra sẽ không tốn nhiều công sức, nhưng chúng ta sẽ đạt được hiệu suất tốt hơn trong khi tua theo trình tự theo cách này. Các khung hình khá nhỏ, chỉ 640x400, nhưng bạn sẽ chỉ nhìn thấy những khung hình này trong quá trình tua. Khi bạn dừng lại, hình ảnh có độ phân giải cao sẽ tải và nhanh chóng mờ dần.

var canvas = document.createElement('canvas');
canvas.width = imageWidth;
canvas.height = imageHeight;

var ctx = canvas.getContext('2d');
ctx.drawImage(sheet, 0, 0);

var tilesX = imageWidth / tileWidth;
var tilesY = imageHeight / tileHeight;

var canvasPaste = canvas.cloneNode(false);
canvasPaste.width = tileWidth;
canvasPaste.height = tileHeight;

var i, j, canvasPasteTemp, imgData, 
var currentIndex = 0;
var startIndex = index * 16;
for (i = 0; i < tilesY; i++) {
  for (j = 0; j < tilesX; j++) {
    // Store the image data of each tile in the array.
    canvasPasteTemp = canvasPaste.cloneNode(false);
    imgData = ctx.getImageData(j * tileWidth, i * tileHeight, tileWidth, tileHeight);
    canvasPasteTemp.getContext('2d').putImageData(imgData, 0, 0);

    list[ startIndex + currentIndex ] = imgData;

    currentIndex++;
  }
}

Các trang tính sprite được tạo bằng Imagemagick. Đây là một ví dụ đơn giản trên GitHub cho thấy cách tạo một ảnh sprite chứa tất cả hình ảnh trong một thư mục.

Tạo ảnh động cho mô-đun

Để đặt các mô-đun trên tiến trình, một đại diện ẩn của tiến trình, được hiển thị ngoài màn hình, theo dõi "đầu phát" và chiều rộng của dòng thời gian. Bạn có thể thực hiện điều này chỉ với mã nhưng tốt với cách trình bày trực quan khi phát triển và gỡ lỗi. Khi chạy thực tế, nó chỉ được cập nhật về đổi kích thước để đặt kích thước. Một số mô-đun lấp đầy khung nhìn và một số có tỷ lệ riêng, vì vậy hơi khó khăn khi mở rộng và đặt mọi thứ ở tất cả các độ phân giải để mọi thứ đều hiển thị và không bị cắt quá nhiều. Mỗi mô-đun có hai chỉ báo tiến trình, một chỉ báo cho vị trí hiển thị trên màn hình và một cho biết thời lượng của chính mô-đun. Khi thực hiện chuyển động thị sai, thường rất khó để tính toán vị trí bắt đầu và kết thúc của đối tượng để đồng bộ với vị trí dự kiến khi đối tượng đó được quan sát. Bạn nên biết chính xác thời điểm một mô-đun vào khung hiển thị, phát dòng thời gian nội bộ của mô-đun đó và thời điểm mô-đun lại chuyển động ra khỏi khung hiển thị.

Mỗi mô-đun có một lớp màu đen tinh tế ở trên cùng giúp điều chỉnh độ mờ để mô-đun hoàn toàn trong suốt khi ở vị trí giữa. Việc này giúp bạn tập trung vào một mô-đun tại một thời điểm, nhờ đó nâng cao trải nghiệm.

Hiệu suất trang

Việc chuyển từ một nguyên mẫu hoạt động sang phiên bản phát hành không có hiện tượng giật có nghĩa là đi từ phỏng đoán sang biết điều gì sẽ xảy ra trong trình duyệt. Đây là nơi Công cụ của Chrome cho nhà phát triển là người bạn thân thiết nhất của bạn.

Chúng tôi đã dành khá nhiều thời gian để tối ưu hoá trang web này. Buộc tăng tốc phần cứng là một trong những công cụ quan trọng nhất dĩ nhiên để có được ảnh động mượt mà. Tuy nhiên, cũng tìm kiếm các cột nhiều màu sắc và hình chữ nhật màu đỏ trong Công cụ của Chrome cho nhà phát triển. Có rất nhiều bài viết hay về các chủ đề này và bạn nên đọc tất cả. Phần thưởng cho việc xoá các khung hình bỏ qua sẽ xuất hiện ngay lập tức nhưng sự thất vọng cũng vậy khi người dùng quay lại. Và họ sẽ làm được điều đó. Đây là một quy trình liên tục và cần được lặp lại.

Tôi thích sử dụng TweenMax của Greensock cho các thuộc tính tweening, chuyển đổi và CSS. Tư duy trong vùng chứa, trực quan hoá cấu trúc của bạn khi bạn thêm các lớp mới. Lưu ý rằng các phép biến đổi hiện tại có thể ghi đè các phép biến đổi mới. TranslateZ(0) buộc tăng tốc phần cứng trong lớp CSS của bạn được thay thế bằng ma trận 2D nếu bạn chỉ lấy giá trị 2D. Để giữ cho lớp ở chế độ tăng tốc trong những trường hợp đó, hãy sử dụng thuộc tính “force3D:true” trong phần tween để tạo ma trận 3D thay vì ma trận 2D. Rất dễ bị quên khi bạn kết hợp CSS và JavaScript tween để đặt kiểu.

Không buộc tăng tốc phần cứng khi không cần thiết. Bộ nhớ GPU có thể nhanh chóng lấp đầy và gây ra kết quả không mong muốn khi bạn muốn tăng tốc phần cứng nhiều vùng chứa, đặc biệt là trên iOS, nơi bộ nhớ có nhiều hạn chế hơn. Việc tải các thành phần nhỏ hơn và mở rộng quy mô bằng CSS và tắt một số hiệu ứng trong chế độ thiết bị di động đã thực hiện những cải tiến đáng kể.

Rò rỉ bộ nhớ là một trường khác mà chúng tôi cần cải thiện kỹ năng của mình. Khi điều hướng giữa các WebGL khác nhau trải nghiệm rất nhiều đối tượng, vật liệu, kết cấu và hình học được tạo ra. Nếu các tệp đó chưa sẵn sàng để thu gom rác khi bạn rời đi và xoá phần đó, thì chúng có thể sẽ khiến thiết bị gặp sự cố sau một thời gian khi hết bộ nhớ.

Thoát khỏi phần có chức năng loại bỏ không thành công.
Thoát khỏi phần có chức năng loại bỏ không thành công.
Tốt hơn nhiều!
Tốt hơn nhiều!

Để tìm ra nơi rò rỉ, quy trình công việc diễn ra khá đơn giản trong Công cụ cho nhà phát triển, ghi lại tiến trình và chụp ảnh chụp nhanh của vùng nhớ khối xếp. Sẽ dễ dàng hơn nếu bạn có thể lọc ra các đối tượng cụ thể như hình học 3D hoặc một thư viện cụ thể. Trong ví dụ trên, hoá ra cảnh 3D vẫn còn tồn tại và một mảng hình học đã lưu trữ không bị xoá. Nếu bạn thấy khó xác định vị trí của đối tượng, có một tính năng tuyệt vời cho phép bạn xem tính năng này được gọi là giữ lại đường dẫn. Chỉ cần nhấp vào đối tượng bạn muốn kiểm tra trong ảnh chụp nhanh của vùng nhớ khối xếp để xem thông tin trong bảng điều khiển dưới đây. Việc sử dụng cấu trúc hợp lệ với các đối tượng nhỏ hơn sẽ giúp ích khi xác định vị trí tệp tham chiếu của bạn.

Cảnh này được tham chiếu trong EffectComposer.
Cảnh được tham chiếu trong EffectComposer.

Nhìn chung, bạn nên suy nghĩ kỹ trước khi thao túng DOM. Khi thực hiện, hãy nghĩ đến tính hiệu quả. Đừng thao túng DOM bên trong vòng lặp trò chơi nếu bạn có thể giúp. Lưu trữ tệp tham chiếu trong các biến để sử dụng lại. Nếu bạn cần tìm một phần tử, hãy sử dụng tuyến ngắn nhất bằng cách lưu trữ thông tin tham chiếu đến các vùng chứa chiến lược và tìm kiếm bên trong phần tử đối tượng cấp trên gần nhất.

Trì hoãn việc đọc kích thước của các phần tử mới thêm hoặc khi xoá/thêm lớp nếu bạn gặp lỗi bố cục. Hoặc đảm bảo Layout đã được kích hoạt. Đôi khi, trình duyệt thay đổi hàng loạt kiểu và sẽ không cập nhật sau lần kích hoạt bố cục tiếp theo. Đôi khi đây thực sự là một vấn đề lớn, nhưng nó hoàn toàn có lý do. Vì vậy, hãy cố gắng tìm hiểu cách hoạt động của nó ở hậu trường và bạn sẽ nhận được rất nhiều lợi ích.

Toàn màn hình

Khi có sẵn, bạn có tuỳ chọn để đặt trang web ở chế độ toàn màn hình trong trình đơn thông qua API Fullscreen. Tuy nhiên, trình duyệt cũng có thể quyết định chuyển video sang chế độ toàn màn hình trên các thiết bị. Safari trên iOS trước đây có lỗ hổng bảo mật cho phép bạn kiểm soát điều đó, nhưng tính năng này hiện không còn khả dụng nữa. Vì vậy, bạn phải chuẩn bị thiết kế của mình để hoạt động mà không cần nó khi tạo một trang không cuộn. Chúng ta có thể sẽ nhận được thông tin cập nhật về tính năng này trong các bản cập nhật sau này, vì tính năng này đã phá vỡ rất nhiều ứng dụng web.

Thành phần

Ảnh động hướng dẫn cho thử nghiệm.
Hình ảnh hướng dẫn cho thử nghiệm.

Trên trang web, chúng tôi có nhiều loại thành phần. Chúng tôi sử dụng hình ảnh (PNG và JPEG), SVG (nội tuyến và nền), sprite (PNG), phông chữ biểu tượng tuỳ chỉnh và ảnh động của Adobe Edge. Chúng tôi dùng PNG cho các thành phần và ảnh động (sprite) mà phần tử không thể dựa trên vectơ, nếu không, chúng tôi cố gắng sử dụng SVG nhiều nhất có thể.

Định dạng vectơ có nghĩa là không làm giảm chất lượng, ngay cả khi chúng tôi điều chỉnh tỷ lệ vectơ. 1 tệp cho mọi thiết bị.

  • Kích thước tệp nhỏ.
  • Chúng ta có thể tạo ảnh động riêng biệt cho từng phần (rất phù hợp với ảnh động nâng cao). Ví dụ: chúng tôi ẩn "phụ đề" của biểu trưng Hobbit (sự tàn phá của coroutine) khi biểu trưng này bị thu nhỏ.
  • Trang này có thể được nhúng dưới dạng thẻ HTML SVG hoặc dùng làm hình nền mà không cần tải thêm (được tải cùng lúc với trang html).

Kiểu chữ biểu tượng có ưu điểm tương tự như SVG khi nói đến khả năng có thể mở rộng và được dùng thay cho SVG đối với các phần tử nhỏ như biểu tượng mà chúng ta chỉ cần thay đổi màu (di chuột, đang hoạt động, v.v.). Các biểu tượng cũng rất dễ sử dụng lại, bạn chỉ cần đặt thuộc tính "content" CSS của một phần tử.

Hoạt ảnh

Trong một số trường hợp, việc tạo ảnh động cho các phần tử SVG bằng mã có thể tốn rất nhiều thời gian, đặc biệt là khi ảnh động cần được thay đổi nhiều trong quá trình thiết kế. Để cải thiện quy trình làm việc giữa nhà thiết kế và nhà phát triển, chúng tôi sử dụng Adobe Edge cho một số ảnh động (hướng dẫn trước khi chơi trò chơi). Quy trình làm việc của ảnh động thực sự gần giống với Flash và điều đó giúp ích cho nhóm nhưng có một vài hạn chế, đặc biệt là khi tích hợp ảnh động Edge vào quá trình tải nội dung vì nó đi kèm với trình tải và logic triển khai riêng.

Tôi vẫn cảm thấy chúng ta vẫn còn một chặng đường dài trước khi có được quy trình làm việc hoàn hảo để xử lý thành phần và ảnh động thủ công trên web. Chúng tôi rất mong được thấy những công cụ như Edge sẽ phát triển như thế nào. Bạn có thể đóng góp ý kiến về các công cụ tạo ảnh động và quy trình công việc khác trong phần nhận xét.

Kết luận

Bây giờ, khi tất cả các phần của dự án đã được phát hành và chúng ta nhìn vào kết quả cuối cùng, phải nói rằng chúng ta khá ấn tượng với tình trạng của các trình duyệt hiện đại dành cho thiết bị di động. Khi bắt đầu dự án này, chúng tôi kỳ vọng thấp hơn rất nhiều về mức độ liền mạch, tích hợp và hiệu suất mà chúng tôi có thể tạo ra. Đây là một trải nghiệm học tập tuyệt vời đối với chúng tôi và toàn bộ thời gian lặp lại và thử nghiệm (rất nhiều) đã giúp chúng tôi nâng cao hiểu biết về cách thức hoạt động của các trình duyệt hiện đại. Và đó là những gì sẽ xảy ra nếu chúng ta muốn rút ngắn thời gian sản xuất của những loại dự án này, từ suy đoán đến biết.