Hướng dẫn từng bước về cách phát triển trên nhiều thiết bị
Trong bài viết đầu tiên về quá trình phát triển Thử nghiệm Chrome A Journey Through Middle-earth (Chuyến phiêu lưu 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 sẽ 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 giao diện người dùng HTML5 còn lại.
Ba phiên bản của cùng một trang web
Hãy bắt đầu bằng cách 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 theo quan điểm về kích thước màn hình và chức năng của thiết bị.
Toàn bộ dự án được xây dựng dựa trên phong cách rất "điện ảnh", trong đó chúng tôi muốn giữ trải nghiệm trong một khung cố định hướng ngang để giữ được sự kỳ diệu của bộ phim. Vì phần lớn dự án bao gồm các "trò chơi" nhỏ tương tác, nên việc để chúng tràn khung cũng không hợp lý.
Chúng ta có thể lấy trang đích làm ví dụ về cách điều chỉnh thiết kế cho nhiều kích thước.
Trang web có 3 chế độ: máy tính, 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 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à điều dễ dàng.
Chúng tôi đang 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 thử kích thước khung nhìn để nhắm đến máy tính bảng trong số đó (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 nội dung nghe nhìn hoặc vị trí tương đối/theo tỷ lệ phần trăm bằng JavaScript.
Vì 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á độc đáo giữa các phần khác nhau, nên việc sử dụng điểm ngắt hoặc kiểu nào còn tuỳ thuộc vào phần tử và trường hợp cụ thể. Đã có nhiều lần chúng ta thiết lập bố cục hoàn hảo bằng các sass-mixins và media-query đẹp mắt, sau đó cần thêm hiệu ứng dựa trên vị trí con trỏ hoặc đối tượng động, và cuối cùng phải viết lại mọi thứ bằng JavaScript.
Chúng ta cũng thêm một lớp có chế độ hiện tại trong thẻ head để có thể sử dụng thông tin đó trong các kiểu của mình, 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ả kích thước xuống đến khoảng 360x320. Đây là một thách thức khá lớn khi tạo trải nghiệm web sống động. Trên máy tính, 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 một 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à 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. Ý kiến phản đối là chế độ dọc không mang lại cảm giác sống động như chế độ ngang; nhưng trang web này có thể mở rộng khá tốt nên chúng tôi vẫn giữ nguyên.
Điều quan trọng cần lưu ý là bạn không nên nhầm lẫn bố cục với tính năng phát hiện tính năng như loại đầ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 trong tất cả các chế độ này và phải trải dài trên tất cả. Một ví dụ là hỗ trợ chuột và cảm ứng cùng lúc. Màn hình Retina bù lại chất lượng nhưng quan trọng nhất là hiệu suất, đôi khi chất lượng thấp hơn lại tốt hơn. Ví dụ: canvas có độ phân giải bằng một nửa độ phân giải trong trải nghiệm WebGL trên màn hình retina, nếu không thì phải kết xuất gấp bốn lần số pixel
Chúng tôi thường xuyên sử dụng công cụ trình mô phỏng trong DevTools trong quá trình phát triển, đặc biệt là trong Chrome Canary có các tính năng mới được cải tiến và nhiều chế độ cài đặ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 các thiết bị thực. Một lý do là do trang web đang điều chỉnh để hiển thị ở chế độ toàn màn hình. Các trang có thanh 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 đề về điều này), nhưng chúng tôi phải điều chỉnh mọi thứ độc lập với điều đó. 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ẽ đến bản đồ của Trung địa. Bạn có nhận 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à một đối tượng riêng kế thừa một mẫu chức năng như các phần tử DOM, hiệu ứng chuyển đổi, tải thành phầ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 khởi tạo, các phần tử sẽ được thêm vào và xoá khỏi DOM và các thành phần cho phần hiện tại sẽ được tải.
Vì người dùng có thể nhấn nút quay lại của trình duyệt hoặc điều hướng qua trình đơn bất cứ lúc nào, nên mọi nội dung được tạo đều cần được xử lý tại một thời điểm nào đó. Bạn cần dừng và loại bỏ các hoạt ảnh và thời gian chờ, nếu không chúng sẽ gây ra hành vi không mong muốn, lỗi và rò rỉ bộ nhớ. Đây không phải lúc nào cũng là một nhiệm vụ dễ dàng, đặc biệt là khi thời hạn sắp đến và bạn cần phải hoàn tất mọi việc nhanh nhất có thể.
Giới thiệu các vị trí
Để khoe những cảnh đẹp và nhân vật của Trung địa, chúng tôi đã xây dựng một hệ thống mô-đun gồm các thành phần hình ảnh và văn bản mà bạn có thể kéo hoặc vuốt theo chiều ngang. Chúng ta chưa bật thanh cuộn ở đây vì muốn có tốc độ khác nhau trên các dải khác nhau, chẳng hạn như trong các trình tự hình ảnh mà bạn dừng chuyển động sang một bên cho đến khi đoạn video phát hết.
Dòng thời gian
Khi bắt đầu phát triển, chúng tôi không biết nội dung của các mô-đun cho từng vị trí. Chúng tôi biết rằng mình muốn có một mẫu để hiển thị nhiều loại nội dung nghe nhìn và thông tin trong một dòng thời gian ngang, cho phép chúng tôi tự do trình bày 6 vị trí khác nhau mà không cần phải tạo lại mọi thứ 6 lần. Để quản lý việc này, chúng tôi đã tạo một trình điều khiển tiến trình xử lý việc kéo các mô-đun dựa trên chế độ cài đặt và hành vi của các mô-đun.
Mô-đun và thành phần hành vi
Các mô-đun khác nhau mà chúng tôi thêm tính năng hỗ trợ là trình tự hình ảnh, ảnh tĩnh, cảnh thị sai, cảnh chuyển tiêu điểm và văn bản.
Mô-đun cảnh có hiệu ứng thị sai có nền mờ với số lượng lớp tuỳ chỉnh theo dõi tiến trình khung nhìn để biết vị trí chính xác.
Cảnh chuyển tiêu điểm là một biến thể của nhóm hiệu ứng thị sai, ngoài ra, chúng ta sử dụng hai hình ảnh cho mỗi lớp để làm mờ và làm rõ nhằm mô phỏng sự thay đổi tiêu điểm. Chúng tôi đã cố gắng sử dụng bộ lọc làm mờ, nhưng vẫn còn quá tốn kém, vì vậy, chúng ta sẽ đợi chương trình đổ bóng CSS cho việc này.
Nội dung trong mô-đun văn bản được bật tính năng kéo bằng trình bổ trợ TweenMax Draggable. Bạn cũng có thể dùng con lăn chuột hoặc vuốt hai ngón tay để cuộn theo chiều dọc. Lưu ý throw-props-plugin sẽ thêm hiệu ứng vật lý kiểu hất khi bạn vuốt và thả.
Các mô-đun cũng có thể có nhiều hành vi được thêm dưới dạng một tập hợp thành phần. Tất cả đề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 nóng cho lớp phủ thông tin, chỉ số gỡ lỗi để kiểm thử trực quan, lớp phủ tiêu đề bắt đầu, lớp flare và một số lớp phủ khác. Các phần tử 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 việc này, chúng ta có thể tạo nhiều vị trí chỉ bằng một tệp cấu hình xác định tài sản cần tải và thiết lập nhiều loại mô-đun và thành phần.
Trình tự hình ảnh
Trình tự hình ảnh là phần khó khăn nhất của các mô-đun về hiệu suất và kích thước tải xuống. Có rất nhiều bài viết về chủ đề này. Trên thiết bị di động và máy tính bảng, chúng tôi sẽ thay thế ảnh động này bằng hình ảnh tĩnh. Nếu muốn có chất lượng tốt trên thiết bị di động, chúng ta sẽ phải giải mã và lưu trữ quá nhiều dữ liệu trong bộ nhớ. Chúng tôi đã thử nhiều giải pháp thay thế; trước tiên, hãy sử dụng hình nền và một trang ảnh động, 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 trang ảnh động. 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ừ một trang ảnh động sang canvas có hiệu suất 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 hình, 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 đó, bạn có thể thu thập rác cho trang ảnh động ban đầu và 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ữ hình ảnh chưa giải mã thực sự ít hơn, nhưng chúng ta sẽ có hiệu suất tốt hơn khi quét sạch trình tự theo cách này. Các khung hình khá nhỏ, chỉ 640x400, nhưng chỉ xuất hiện trong quá trình cuộn. Khi bạn dừng, một 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 ảnh động được tạo bằng Imagemagick. Dưới đây là một ví dụ đơn giản trên GitHub cho thấy cách tạo một trang ảnh động của tất cả hình ảnh bên trong một thư mục.
Tạo ảnh động cho các mô-đun
Để đặt các mô-đun trên dòng thời gian, một bản trình bày ẩn của dòng thời gian (hiển thị ngoài màn hình) sẽ 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 việc này chỉ bằng mã, nhưng tốt hơn là bạn nên có hình ảnh trực quan khi phát triển và gỡ lỗi. Khi chạy thực sự, kích thước chỉ được cập nhật khi đổ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ố mô-đun có tỷ lệ riêng, vì vậy, việc điều chỉnh tỷ lệ và định vị mọi thứ ở tất cả độ phân giải để mọi thứ đều hiển thị và không bị cắt quá nhiều là một chút khó khăn. 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 chỉ báo cho thời lượng của chính mô-đun đó. Khi tạo hiệu ứng chuyển động song song, bạn thường khó tính toán vị trí bắt đầu và kết thúc của các đối tượng để đồng bộ hoá với vị trí dự kiến khi đối tượng đó hiển thị. 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 tiến trình nội bộ và thời điểm mô-đun đó tạo ảnh động để rời khỏi khung hiển thị.
Mỗi mô-đun có một lớp màu đen tinh tế ở trên cùng để điều chỉnh độ mờ, nhờ đó mô-đun sẽ hoàn toàn trong suốt khi ở vị trí chính giữa. Điều 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ừ nguyên mẫu đang hoạt động sang phiên bản phát hành không bị giật có nghĩa là chuyển từ việc đoán sang việc biết được những gì xảy ra trong trình duyệt. Đây là lúc bạn cần đến Công cụ của Chrome cho nhà phát triển.
Chúng tôi đã dành khá nhiều thời gian để tối ưu hoá trang web. Tất nhiên, việc buộc tăng tốc phần cứng là một trong những công cụ quan trọng nhất để có được ảnh động mượt mà. Nhưng cũng tìm kiếm các cột đầy 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ó nhiều bài viết hay về các chủ đề này và bạn nên đọc tất cả các bài viết đó. Phần thưởng cho việc loại bỏ các khung hình bị bỏ qua là tức thì, nhưng sự thất vọng cũng vậy khi chúng quay lại. Và họ sẽ làm được. Đây là một quy trình liên tục cần lặp lại.
Tôi thích sử dụng TweenMax của Greensock để tạo hiệu ứng chuyển đổi cho các thuộc tính, phép biến đổi và CSS. Hãy suy nghĩ trong các vùng chứa, hình dung cấu trúc của bạn khi bạn thêm các lớp mới. Xin lưu ý rằng các phép biến đổi mới có thể ghi đè các phép biến đổi hiện có. translateZ(0) buộc tăng tốc phần cứng trong lớp CSS của bạn sẽ được thay thế bằng ma trận 2D nếu bạn chỉ tween các 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 tween để tạo ma trận 3D thay vì ma trận 2D. Bạn có thể dễ dàng quên khi kết hợp các tween CSS và JavaScript để đặ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 đầ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 cho nhiều vùng chứa, đặc biệt là trên iOS, nơi bộ nhớ có nhiều quy tắc ràng buộc hơn. Việc tải các thành phần nhỏ hơn và mở rộng các thành phần đó bằng css cũng như tắt một số hiệu ứng ở chế độ di động đã mang lại những cải tiến đáng kể.
Rò rỉ bộ nhớ là một lĩnh vực khác mà chúng tôi cần cải thiện kỹ năng. Khi di chuyển giữa các trải nghiệm WebGL khác nhau, nhiều đối tượng, chất liệu, hoạ tiết và hình học sẽ được tạo. Nếu các phần tử đó chưa sẵn sàng cho việc thu gom rác khi bạn rời khỏi và xoá phần này, thì chúng có thể khiến thiết bị gặp sự cố sau một thời gian khi hết bộ nhớ.
Để tìm lỗi rò rỉ, bạn có thể làm theo quy trình khá đơn giản trong DevTools, ghi lại tiến trình và chụp ảnh nhanh vùng nhớ khối xếp. Việc này sẽ dễ dàng hơn nếu bạn có thể lọc ra các đối tượng cụ thể, chẳng hạn như hình học 3D hoặc một thư viện cụ thể. Trong ví dụ trên, cảnh 3D vẫn còn và một mảng lưu trữ hình học cũng không bị xoá. Nếu bạn khó xác định vị trí của đối tượng, thì có một tính năng thú vị cho phép bạn xem đối tượng này có tên là giữ lại đường dẫn. Bạn chỉ cần nhấp vào đối tượng mà bạn muốn kiểm tra trong ảnh chụp nhanh vùng nhớ khối xếp và bạn sẽ nhận được thông tin trong bảng điều khiển bên dưới. Việc sử dụng một cấu trúc tốt với các đối tượng nhỏ hơn sẽ giúp bạn định vị các tệp tham chiếu.
Nhìn chung, bạn nên suy nghĩ kỹ trước khi thao tác với DOM. Khi làm như vậy, hãy nghĩ đến tính hiệu quả. Đừng thao tác với DOM bên trong vòng lặp trò chơi nếu có thể. Lưu trữ các tệp tham chiếu trong 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 ngắn nhất bằng cách lưu trữ các tệp tham chiếu đến vùng chứa chiến lược và tìm kiếm bên trong phần tử 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 Bố cục được kích hoạt. Đôi khi, trình duyệt sẽ thay đổi hàng loạt thành kiểu và sẽ không cập nhật sau điều kiện kích hoạt bố cục tiếp theo. Đôi khi, đây có thể là một vấn đề lớn, nhưng có lý do khiến nó xảy ra. 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ẽ thu được nhiều lợi ích.
Toàn màn hình
Khi có, bạn có thể đặt trang web ở chế độ toàn màn hình trong trình đơn thông qua API Toàn màn hình. Tuy nhiên, trên các thiết bị, trình duyệt cũng có quyết định chuyển sang chế độ toàn màn hình. Safari trên iOS trước đây có một tính năng để cho phép bạn kiểm soát điều đó, nhưng tính năng đó không còn nữa nên bạn phải chuẩn bị thiết kế để hoạt động mà không cần tính năng đó khi tạo trang không cuộn. Chúng tôi có thể sẽ cập nhật thông tin về vấn đề này trong các bản cập nhật trong tương lai, vì vấn đề này đã làm hỏng nhiều ứng dụng web.
Thành phần
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), trang tổng hợp sprite (PNG), phông chữ biểu tượng tuỳ chỉnh và ảnh động Adobe Edge. Chúng tôi sử dụng PNG cho các thành phần và ảnh động (trang ảnh động) mà phần tử không thể dựa trên vectơ, nếu không chúng tôi sẽ 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 ta điều chỉnh tỷ lệ. 1 tệp cho tất cả thiết bị.
- Kích thước tệp nhỏ.
- Chúng ta có thể tạo ảnh động cho từng phần riêng biệt (phù hợp với ảnh động nâng cao). Ví dụ: chúng ta ẩn "phần phụ đề" của biểu trưng Hobbit (Smaug bị tàn phá) khi biểu trưng này được thu nhỏ.
- Bạn có thể nhúng dưới dạng thẻ HTML SVG hoặc sử dụng dưới dạng hình nền mà không cần tải thêm (hình ảnh được tải cùng lúc với trang html).
Phông chữ biểu tượng có các ưu điểm tương tự như SVG về khả năng mở rộng và được dùng thay cho SVG cho các phần tử nhỏ như biểu tượng mà chúng ta chỉ cần thay đổi màu sắc (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ử.
Ảnh động
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ể rất tốn 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 rất giống với Flash và điều này đã giúp ích cho nhóm, nhưng vẫn có một số hạn chế, đặc biệt là khi tích hợp ảnh động Edge trong quá trình tải tài sản vì ảnh động này đ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 còn phải đi 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ý các thành phần và ảnh động thủ công trên web. Chúng tôi rất mong được xem cách các công cụ như Edge phát triển. Bạn có thể thêm đề xuất về các công cụ và quy trình làm việc khác về ảnh động trong phần bình luận.
Kết luận
Giờ đây, khi tất cả các phần của dự án được phát hành và chúng ta xem xét kết quả cuối cùng, tôi phải nói rằng chúng ta khá ấn tượng với trạng thái của các trình duyệt di động hiện đại. Khi bắt đầu dự án này, chúng tôi có kỳ vọng thấp hơn 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 được. Đây là một trải nghiệm học tập tuyệt vời đối với chúng tôi và tất cả thời gian lặp lại và thử nghiệm (rất nhiều) đã giúp chúng tôi hiểu rõ hơn về cách hoạt động của các trình duyệt hiện đại. Đó là những gì cần làm nếu chúng ta muốn rút ngắn thời gian sản xuất cho những loại dự án này, từ việc phỏng đoán sang việc biết chắc chắn.