Xây dựng với Chrome

Đưa các viên gạch LEGO® lên web đa thiết bị

Hans Eklund
Hans Eklund

Build with Chrome (Tạo bằng Chrome) là một thử nghiệm thú vị dành cho người dùng Chrome trên máy tính ban đầu được ra mắt ở Úc. Ứng dụng này được phát hành lại vào năm 2014, cung cấp dịch vụ trên toàn cầu, liên kết với THE LEGO® MOVIE™ và hỗ trợ mới cho thiết bị di động. Trong bài viết này, chúng tôi sẽ chia sẻ một số bài học rút ra được từ dự án, đặc biệt là về việc chuyển từ trải nghiệm chỉ dành cho máy tính sang giải pháp nhiều màn hình hỗ trợ cả phương thức nhập bằng chuột và cảm ứng.

Lịch sử của tính năng Tạo bằng Chrome

Phiên bản đầu tiên của tính năng Tạo bằng Chrome được ra mắt ở Úc vào năm 2012. Chúng tôi muốn thể hiện sức mạnh của web theo một cách hoàn toàn mới và mang Chrome đến với một đối tượng hoàn toàn mới.

Trang web này có hai phần chính: chế độ "Tạo" để người dùng có thể tạo các tác phẩm bằng cách sử dụng các viên gạch LEGO và chế độ "Khám phá" để duyệt xem các tác phẩm trên phiên bản Google Maps được tạo bằng LEGO.

Hình ảnh 3D tương tác là yếu tố cần thiết để mang lại cho người dùng trải nghiệm xây dựng LEGO tốt nhất. Vào năm 2012, WebGL chỉ được cung cấp công khai trong các trình duyệt dành cho máy tính, vì vậy, Build được nhắm đến là một trải nghiệm chỉ dành cho máy tính. Tính năng Khám phá sử dụng Google Maps để hiển thị các bản dựng, nhưng khi phóng to đủ gần, tính năng này sẽ chuyển sang triển khai WebGL của bản đồ hiển thị các bản dựng ở chế độ 3D, vẫn sử dụng Google Maps làm hoạ tiết nền tảng. Chúng tôi hy vọng có thể xây dựng một môi trường nơi những người đam mê LEGO ở mọi lứa tuổi có thể dễ dàng và trực quan thể hiện sự sáng tạo của mình cũng như khám phá các tác phẩm sáng tạo của nhau.

Năm 2013, chúng tôi quyết định mở rộng Build with Chrome sang các công nghệ web mới. Trong số các công nghệ đó có WebGL trong Chrome dành cho Android. Công nghệ này sẽ cho phép tính năng Tạo bằng Chrome phát triển thành một trải nghiệm dành cho thiết bị di động. Để bắt đầu, trước tiên, chúng tôi đã phát triển các nguyên mẫu cảm ứng trước khi đặt câu hỏi về phần cứng cho "Công cụ của trình tạo" để hiểu hành vi cử chỉ và khả năng phản hồi xúc giác mà chúng ta có thể gặp phải thông qua trình duyệt so với ứng dụng di động.

Giao diện người dùng thích ứng

Chúng tôi cần hỗ trợ các thiết bị có cả phương thức nhập bằng chuột và cảm ứng. Tuy nhiên, việc sử dụng cùng một giao diện người dùng trên màn hình cảm ứng nhỏ lại là một giải pháp không tối ưu do hạn chế về không gian.

Trong Build có rất nhiều hoạt động tương tác: thu phóng, thay đổi màu sắc của viên gạch và tất nhiên là chọn, xoay và đặt viên gạch. Đây là một công cụ mà người dùng thường dành nhiều thời gian để sử dụng. Vì vậy, điều quan trọng là họ có thể truy cập nhanh vào mọi thứ họ thường dùng và cảm thấy thoải mái khi tương tác với công cụ này.

Khi thiết kế một ứng dụng cảm ứng có tính tương tác cao, bạn sẽ thấy màn hình nhanh chóng trở nên nhỏ và ngón tay của người dùng có xu hướng che khuất nhiều màn hình trong khi tương tác. Điều này trở nên rõ ràng khi chúng tôi làm việc với Trình tạo. Bạn thực sự phải cân nhắc kích thước màn hình thực tế thay vì số pixel trong đồ hoạ khi thiết kế. Điều quan trọng là bạn phải giảm thiểu số lượng nút và chế độ điều khiển để dành nhiều không gian màn hình nhất có thể cho nội dung thực tế.

Mục tiêu của chúng tôi là tạo cảm giác tự nhiên cho Build trên các thiết bị cảm ứng, không chỉ thêm phương thức nhập bằng cảm ứng vào cách triển khai ban đầu trên máy tính, mà còn tạo cảm giác như thể Build thực sự được thiết kế để sử dụng bằng cảm ứng. Cuối cùng, chúng tôi có hai biến thể giao diện người dùng, một cho máy tính để bàn và máy tính bảng có màn hình lớn và một cho thiết bị di động có màn hình nhỏ hơn. Khi có thể, tốt nhất bạn nên sử dụng một phương thức triển khai và chuyển đổi linh hoạt giữa các chế độ. Trong trường hợp của chúng tôi, chúng tôi xác định rằng có sự khác biệt đáng kể về trải nghiệm giữa hai chế độ này nên chúng tôi quyết định dựa vào một điểm ngắt cụ thể. Hai phiên bản có nhiều tính năng chung và chúng tôi đã cố gắng thực hiện hầu hết các thao tác chỉ bằng một cách triển khai mã, nhưng một số khía cạnh của giao diện người dùng hoạt động khác nhau giữa hai phiên bản.

Chúng tôi sử dụng dữ liệu user-agent để phát hiện thiết bị di động, sau đó kiểm tra kích thước khung nhìn để quyết định có nên sử dụng giao diện người dùng di động màn hình nhỏ hay không. Bạn sẽ gặp khó khăn khi chọn điểm ngắt cho "màn hình lớn" vì khó có thể lấy được giá trị đáng tin cậy về kích thước màn hình thực tế. May mắn thay, trong trường hợp của chúng ta, việc hiển thị giao diện người dùng màn hình nhỏ trên thiết bị cảm ứng có màn hình lớn không thực sự quan trọng vì công cụ này vẫn hoạt động tốt, chỉ là một số nút có thể cảm thấy hơi lớn. Cuối cùng, chúng ta đặt điểm ngắt thành 1000 pixel; nếu tải trang web từ một cửa sổ rộng hơn 1000 pixel (ở chế độ ngang), bạn sẽ nhận được phiên bản màn hình lớn.

Hãy cùng nói một chút về hai kích thước màn hình và trải nghiệm:

Màn hình lớn, hỗ trợ chuột và cảm ứng

Phiên bản màn hình lớn được phân phát cho tất cả máy tính có hỗ trợ chuột và cho các thiết bị cảm ứng có màn hình lớn (chẳng hạn như Google Nexus 10). Phiên bản này gần giống với giải pháp gốc dành cho máy tính về loại chế độ điều khiển điều hướng có sẵn, nhưng chúng tôi đã thêm tính năng hỗ trợ cảm ứng và một số cử chỉ. Chúng ta điều chỉnh giao diện người dùng tuỳ thuộc vào kích thước cửa sổ, vì vậy, khi người dùng đổi kích thước cửa sổ, một số giao diện người dùng có thể bị xoá hoặc đổi kích thước. Chúng ta thực hiện việc này bằng cách sử dụng truy vấn nội dung nghe nhìn CSS.

Ví dụ: Khi chiều cao có sẵn thấp hơn 730 pixel, thanh điều khiển thu phóng ở chế độ Khám phá sẽ bị ẩn:

@media only screen and (max-height: 730px) {
    .zoom-slider {
        display: none;
    }
}

Màn hình nhỏ, chỉ hỗ trợ cảm ứng

Phiên bản này được phân phát cho thiết bị di động và máy tính bảng nhỏ (thiết bị mục tiêu Nexus 4 và Nexus 7). Phiên bản này yêu cầu hỗ trợ cảm ứng đa điểm.

Trên các thiết bị màn hình nhỏ, chúng ta cần cung cấp cho nội dung nhiều không gian màn hình nhất có thể. Vì vậy, chúng tôi đã thực hiện một số điều chỉnh để tối đa hoá không gian, chủ yếu là bằng cách di chuyển các phần tử ít dùng ra khỏi tầm nhìn:

  • Bộ chọn khối bản dựng sẽ thu nhỏ thành bộ chọn màu trong khi tạo.
  • Chúng tôi đã thay thế các nút điều khiển thu phóng và hướng bằng cử chỉ nhiều điểm chạm.
  • Chức năng toàn màn hình của Chrome cũng hữu ích để có thêm không gian màn hình.
Xây dựng trên màn hình lớn
Tạo trên màn hình lớn. Trình chọn khối luôn hiển thị và có một số tùy chọn điều khiển ở bên phải.
Xây dựng trên màn hình nhỏ
Tạo trên màn hình nhỏ. Trình chọn khối được thu nhỏ và một số nút đã bị xoá.

Hiệu suất và khả năng hỗ trợ WebGL

Các thiết bị cảm ứng hiện đại có GPU khá mạnh, nhưng vẫn còn thua xa so với các thiết bị tương tự trên máy tính. Vì vậy, chúng tôi biết rằng sẽ gặp một số thách thức về hiệu suất, đặc biệt là ở chế độ Khám phá 3D, nơi chúng tôi cần kết xuất nhiều tác phẩm cùng một lúc.

Về mặt sáng tạo, chúng tôi muốn thêm một vài loại gạch mới có hình dạng phức tạp và thậm chí là độ trong suốt – những tính năng thường rất nặng đối với GPU. Tuy nhiên, chúng tôi phải tương thích ngược và tiếp tục hỗ trợ các bản dựng từ phiên bản đầu tiên, vì vậy, chúng tôi không thể đặt bất kỳ quy định hạn chế mới nào, chẳng hạn như giảm đáng kể tổng số viên gạch trong bản dựng.

Trong phiên bản đầu tiên của Build, chúng tôi có giới hạn tối đa về số lượng khối có thể sử dụng trong một bản dựng. Có một "đồng hồ gạch" cho biết số lượng gạch còn lại. Trong quá trình triển khai mới, chúng tôi đã có một số khối mới ảnh hưởng đến đồng hồ khối nhiều hơn so với các khối tiêu chuẩn, do đó làm giảm nhẹ tổng số khối tối đa. Đây là một cách để đưa các khối mới vào trong khi vẫn duy trì hiệu suất ở mức khá.

Trong chế độ Khám phá 3D, có khá nhiều hoạt động diễn ra cùng một lúc; tải hoạ tiết tấm đế, tải nội dung tạo, tạo ảnh động và kết xuất nội dung tạo, v.v. Điều này đòi hỏi nhiều từ cả GPU và CPU, vì vậy, chúng tôi đã thực hiện nhiều hoạt động phân tích khung hình trong Chrome DevTools để tối ưu hoá các phần này nhiều nhất có thể. Trên thiết bị di động, chúng tôi quyết định phóng to một chút đối với các tác phẩm để không phải kết xuất nhiều tác phẩm cùng một lúc.

Một số thiết bị đã khiến chúng tôi phải xem lại và đơn giản hoá một số chương trình đổ bóng WebGL, nhưng chúng tôi luôn tìm ra cách giải quyết và tiếp tục tiến lên.

Hỗ trợ các thiết bị không có WebGL

Chúng tôi muốn trang web có thể sử dụng được ngay cả khi thiết bị của khách truy cập không hỗ trợ WebGL. Đôi khi, bạn có thể biểu thị hình ảnh 3D theo cách đơn giản bằng cách sử dụng giải pháp canvas hoặc các tính năng CSS3D. Rất tiếc, chúng tôi không tìm thấy giải pháp nào đủ tốt để sao chép các tính năng Tạo và Khám phá 3D mà không cần sử dụng WebGL.

Để đảm bảo tính nhất quán, phong cách hình ảnh của các nội dung sáng tạo phải giống nhau trên tất cả nền tảng. Chúng tôi có thể đã thử giải pháp 2,5D, nhưng điều này sẽ khiến các tác phẩm trông khác đi theo một số cách. Chúng tôi cũng phải cân nhắc cách đảm bảo rằng các nội dung tạo bằng phiên bản đầu tiên của công cụ Tạo bằng Chrome sẽ có giao diện giống nhau và chạy mượt mà trên phiên bản mới của trang web như trên phiên bản đầu tiên.

Bạn vẫn có thể truy cập vào chế độ Khám phá 2D trên các thiết bị không hỗ trợ WebGL, mặc dù bạn không thể tạo tác phẩm mới hoặc khám phá ở chế độ 3D. Vì vậy, người dùng vẫn có thể biết được độ sâu của dự án và những gì họ có thể tạo bằng công cụ này nếu đang sử dụng thiết bị hỗ trợ WebGL. Trang web này có thể không có nhiều giá trị đối với người dùng không hỗ trợ WebGL, nhưng ít nhất cũng đóng vai trò là một bản xem trước và thu hút họ tham gia thử nghiệm.

Đôi khi, bạn không thể giữ lại các phiên bản dự phòng cho các giải pháp WebGL. Có nhiều lý do có thể xảy ra; hiệu suất, kiểu hình ảnh, chi phí phát triển và bảo trì, v.v. Tuy nhiên, khi quyết định không triển khai phương án dự phòng, ít nhất bạn cũng nên quan tâm đến những khách truy cập không bật WebGL, giải thích lý do họ không thể truy cập đầy đủ vào trang web và hướng dẫn cách giải quyết vấn đề bằng cách sử dụng trình duyệt hỗ trợ WebGL.

Quản lý thành phần

Năm 2013, Google đã ra mắt phiên bản mới của Google Maps với những thay đổi quan trọng nhất về giao diện người dùng kể từ khi ra mắt. Vì vậy, chúng tôi quyết định thiết kế lại tính năng Tạo bằng Chrome để phù hợp với giao diện người dùng mới của Google Maps, đồng thời xem xét các yếu tố khác trong quá trình thiết kế lại. Thiết kế mới tương đối phẳng với màu sắc đồng nhất và hình dạng đơn giản. Điều này cho phép chúng tôi sử dụng CSS thuần tuý trên nhiều thành phần giao diện người dùng, giảm thiểu việc sử dụng hình ảnh.

Trong phần Khám phá, chúng ta cần tải nhiều hình ảnh; hình thu nhỏ cho các tác phẩm, kết cấu bản đồ cho các tấm nền và cuối cùng là các tác phẩm 3D thực tế. Chúng tôi đặc biệt chú ý để đảm bảo không có rò rỉ bộ nhớ khi liên tục tải hình ảnh mới.

Các tác phẩm 3D được lưu trữ ở định dạng tệp tuỳ chỉnh được đóng gói dưới dạng hình ảnh PNG. Việc lưu trữ dữ liệu về tác phẩm tạo 3D dưới dạng hình ảnh giúp chúng tôi có thể truyền dữ liệu trực tiếp đến chương trình đổ bóng hiển thị tác phẩm tạo.

Đối với tất cả hình ảnh do người dùng tạo, thiết kế này cho phép chúng tôi sử dụng cùng một kích thước hình ảnh cho tất cả nền tảng, nhờ đó giảm thiểu mức sử dụng bộ nhớ và băng thông.

Quản lý hướng màn hình

Bạn có thể dễ dàng quên tỷ lệ khung hình màn hình thay đổi như thế nào khi chuyển từ chế độ dọc sang chế độ ngang hoặc ngược lại. Bạn cần cân nhắc điều này ngay từ đầu khi điều chỉnh cho thiết bị di động.

Trên một trang web truyền thống đã bật tính năng cuộn, bạn có thể áp dụng các quy tắc CSS để có được một trang web thích ứng sắp xếp lại nội dung và trình đơn. Miễn là bạn có thể sử dụng chức năng cuộn, việc này khá dễ quản lý.

Chúng tôi cũng đã sử dụng phương thức này với Build, nhưng chúng tôi bị hạn chế một chút trong cách giải quyết bố cục, vì chúng tôi cần hiển thị nội dung mọi lúc và vẫn có thể truy cập nhanh vào một số nút điều khiển. Đối với các trang web nội dung thuần tuý như trang web tin tức, bố cục linh hoạt là rất hợp lý, nhưng đối với ứng dụng trò chơi như ứng dụng của chúng tôi thì điều này rất khó khăn. Việc tìm một bố cục hoạt động ở cả hướng ngang và dọc mà vẫn đảm bảo tổng quan tốt về nội dung và cách tương tác thoải mái là một thách thức. Cuối cùng, chúng tôi quyết định chỉ giữ lại Cấu hình ở chế độ ngang và yêu cầu người dùng xoay thiết bị.

Việc giải quyết vấn đề trên trang Khám phá dễ dàng hơn nhiều ở cả hai hướng. Chúng ta chỉ cần điều chỉnh mức thu phóng của chế độ 3D tuỳ theo hướng để có được trải nghiệm nhất quán.

Hầu hết bố cục nội dung đều do CSS kiểm soát, nhưng một số nội dung liên quan đến hướng cần được triển khai trong JavaScript. Chúng tôi nhận thấy không có giải pháp tốt nào trên nhiều thiết bị để sử dụng window.orientation nhằm xác định hướng. Vì vậy, cuối cùng, chúng tôi chỉ so sánh window.innerWidth và window.innerHeight để xác định hướng của thiết bị.

if( window.innerWidth > window.innerHeight ){
  //landscape
} else {
  //portrait
}

Thêm tính năng hỗ trợ cảm ứng

Việc thêm tính năng hỗ trợ thao tác chạm vào nội dung web khá đơn giản. Tính năng tương tác cơ bản, chẳng hạn như sự kiện nhấp, hoạt động giống nhau trên máy tính và thiết bị hỗ trợ cảm ứng, nhưng khi nói đến các hoạt động tương tác nâng cao hơn, bạn cũng cần xử lý các sự kiện chạm: touchstart, touchmove và touchend. Bài viết này trình bày những thông tin cơ bản về cách sử dụng các sự kiện này. Internet Explorer không hỗ trợ sự kiện chạm, mà thay vào đó sử dụng Sự kiện con trỏ (pointerdown, pointermove, pointerup). Sự kiện con trỏ đã được gửi đến W3C để chuẩn hoá nhưng hiện chỉ được triển khai trong Internet Explorer.

Trong chế độ Khám phá 3D, chúng tôi muốn cách điều hướng giống như cách triển khai Google Maps tiêu chuẩn; sử dụng một ngón tay để xoay quanh bản đồ và chụm hai ngón tay để thu phóng. Vì các bản tạo là 3D nên chúng tôi cũng thêm cử chỉ xoay bằng hai ngón tay. Đây thường là một thao tác yêu cầu sử dụng sự kiện chạm.

Bạn nên tránh các hoạt động tính toán nặng, chẳng hạn như cập nhật hoặc kết xuất 3D trong trình xử lý sự kiện. Thay vào đó, hãy lưu trữ dữ liệu đầu vào cảm ứng trong một biến và phản ứng với dữ liệu đầu vào trong vòng lặp kết xuất requestAnimationFrame. Điều này cũng giúp bạn dễ dàng triển khai chuột cùng lúc, bạn chỉ cần lưu trữ các giá trị tương ứng của chuột trong cùng một biến.

Bắt đầu bằng cách khởi chạy một đối tượng để lưu trữ dữ liệu đầu vào và thêm trình nghe sự kiện touchstart. Trong mỗi trình xử lý sự kiện, chúng ta gọi event.preventDefault(). Điều này là để ngăn trình duyệt tiếp tục xử lý sự kiện chạm, điều này có thể gây ra một số hành vi không mong muốn như cuộn hoặc điều chỉnh tỷ lệ toàn bộ trang.

var input = {dragStartX:0, dragStartY:0, dragX:0, dragY:0, dragDX:0, dragDY:0, dragging:false};
plateContainer.addEventListener('touchstart', onTouchStart);

function onTouchStart(event) {
  event.preventDefault();
  if( event.touches.length === 1){
    handleDragStart(event.touches[0].clientX , event.touches[0].clientY);
    //start listening to all needed touchevents to implement the dragging
    document.addEventListener('touchmove', onTouchMove);
    document.addEventListener('touchend', onTouchEnd);
    document.addEventListener('touchcancel', onTouchEnd);
  }
}

function onTouchMove(event) {
  event.preventDefault();
  if( event.touches.length === 1){
    handleDragging(event.touches[0].clientX, event.touches[0].clientY);
  }
}

function onTouchEnd(event) {
  event.preventDefault();
  if( event.touches.length === 0){
    handleDragStop();
    //remove all eventlisteners but touchstart to minimize number of eventlisteners
    document.removeEventListener('touchmove', onTouchMove);
    document.removeEventListener('touchend', onTouchEnd);
    //also listen to touchcancel event to avoid unexpected behavior when switching tabs and some other situations
    document.removeEventListener('touchcancel', onTouchEnd);
  }
}

Chúng ta không thực hiện việc lưu trữ dữ liệu đầu vào trong trình xử lý sự kiện mà thay vào đó là trong các trình xử lý riêng biệt: handleDragStart, handleDragging và handleDragStop. Lý do là chúng ta cũng muốn có thể gọi các phương thức này từ trình xử lý sự kiện chuột. Xin lưu ý rằng mặc dù khó xảy ra, nhưng người dùng có thể sử dụng cả thao tác chạm và chuột cùng một lúc. Thay vì xử lý trực tiếp trường hợp đó, chúng ta chỉ cần đảm bảo không có sự cố nào xảy ra.

function handleDragStart(x ,y ){
  input.dragging = true;
  input.dragStartX = input.dragX = x;
  input.dragStartY = input.dragY = y;
}

function handleDragging(x ,y ){
  if(input.dragging) {
    input.dragDX = x - input.dragX;
    input.dragDY = y - input.dragY;
    input.dragX = x;
    input.dragY = y;
  }
}

function handleDragStop(){
  if(input.dragging) {
    input.dragging = false;
    input.dragDX = 0;
    input.dragDY = 0;
  }
}

Khi tạo ảnh động dựa trên thao tác chạm và di chuyển, bạn cũng nên lưu trữ thao tác di chuyển delta kể từ sự kiện gần nhất. Ví dụ: chúng tôi đã sử dụng thông tin này làm tham số cho tốc độ của máy ảnh khi di chuyển trên tất cả các tấm đế trong tính năng Khám phá, vì bạn không kéo các tấm đế mà thực sự đang di chuyển máy ảnh.

function onAnimationFrame() {
  requestAnimationFrame( onAnimationFrame );

  //execute animation based on input.dragDX, input.dragDY, input.dragX or input.dragY
 /*
  /
  */

  //because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
  input.dragDX=0;
  input.dragDY=0;
}

Ví dụ được nhúng: Kéo một đối tượng bằng các sự kiện chạm. Cách triển khai tương tự như khi kéo bản đồ Khám phá 3D trong tính năng Tạo bằng Chrome: http://cdpn.io/qDxvo

Cử chỉ nhiều điểm chạm

Có một số khung hoặc thư viện, chẳng hạn như Hammer hoặc QuoJS, có thể giúp đơn giản hoá việc quản lý các cử chỉ cảm ứng đa điểm, nhưng nếu bạn muốn kết hợp một số cử chỉ và có toàn quyền kiểm soát, đôi khi tốt nhất bạn nên bắt đầu từ đầu.

Để quản lý cử chỉ chụm và xoay, chúng ta lưu trữ khoảng cách và góc giữa hai ngón tay khi ngón tay thứ hai được đặt trên màn hình:

//variables representing the actual scale/rotation of the object we are affecting
var currentScale = 1;
var currentRotation = 0;

function onTouchStart(event) {
  event.preventDefault();
  if( event.touches.length === 1){
    handleDragStart(event.touches[0].clientX , event.touches[0].clientY);
  }else if( event.touches.length === 2 ){
    handleGestureStart(event.touches[0].clientX, event.touches[0].clientY, event.touches[1].clientX, event.touches[1].clientY );
  }
}

function handleGestureStart(x1, y1, x2, y2){
  input.isGesture = true;
  //calculate distance and angle between fingers
  var dx = x2 - x1;
  var dy = y2 - y1;
  input.touchStartDistance=Math.sqrt(dx*dx+dy*dy);
  input.touchStartAngle=Math.atan2(dy,dx);
  //we also store the current scale and rotation of the actual object we are affecting. This is needed to support incremental rotation/scaling. We can't assume that an object is always the same scale when gesture starts.
  input.startScale=currentScale;
  input.startAngle=currentRotation;
}

Trong sự kiện touchmove, chúng ta liên tục đo khoảng cách và góc giữa hai ngón tay đó. Sau đó, chênh lệch giữa khoảng cách bắt đầu và khoảng cách hiện tại được dùng để đặt tỷ lệ, còn chênh lệch giữa góc bắt đầu và góc hiện tại được dùng để đặt góc.

function onTouchMove(event) {
  event.preventDefault();
  if( event.touches.length  === 1){
    handleDragging(event.touches[0].clientX, event.touches[0].clientY);
  }else if( event.touches.length === 2 ){
    handleGesture(event.touches[0].clientX, event.touches[0].clientY, event.touches[1].clientX, event.touches[1].clientY );
  }
}

function handleGesture(x1, y1, x2, y2){
  if(input.isGesture){
    //calculate distance and angle between fingers
    var dx = x2 - x1;
    var dy = y2 - y1;
    var touchDistance = Math.sqrt(dx*dx+dy*dy);
    var touchAngle = Math.atan2(dy,dx);
    //calculate the difference between current touch values and the start values
    var scalePixelChange = touchDistance - input.touchStartDistance;
    var angleChange = touchAngle - input.touchStartAngle;
    //calculate how much this should affect the actual object
    currentScale = input.startScale + scalePixelChange*0.01;
    currentRotation = input.startAngle+(angleChange*180/Math.PI);
    //upper and lower limit of scaling
    if(currentScale<0.5) currentScale = 0.5;
    if(currentScale>3) currentScale = 3;
  }
}

Bạn có thể sử dụng sự thay đổi khoảng cách giữa mỗi sự kiện touchmove theo cách tương tự như ví dụ về thao tác kéo, nhưng phương pháp đó thường hữu ích hơn khi bạn muốn chuyển động liên tục.

function onAnimationFrame() {
  requestAnimationFrame( onAnimationFrame );
  //execute transform based on currentScale and currentRotation
  /*
  /
  */

  //because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
  input.dragDX=0;
  input.dragDY=0;
}

Bạn cũng có thể bật tính năng kéo đối tượng trong khi thực hiện cử chỉ chụm và xoay nếu muốn. Trong trường hợp đó, bạn sẽ sử dụng điểm giữa hai ngón tay làm dữ liệu đầu vào cho trình xử lý kéo.

Ví dụ về nội dung nhúng: xoay và điều chỉnh tỷ lệ đối tượng ở chế độ 2D. Tương tự như cách triển khai bản đồ trong phần Khám phá: http://cdpn.io/izloq

Hỗ trợ chuột và cảm ứng trên cùng một phần cứng

Hiện nay, có một số máy tính xách tay, chẳng hạn như Chromebook Pixel, hỗ trợ cả phương thức nhập bằng chuột và cảm ứng. Điều này có thể gây ra một số hành vi không mong muốn nếu bạn không cẩn thận.

Một điều quan trọng là bạn không chỉ phát hiện tính năng hỗ trợ cảm ứng rồi bỏ qua phương thức nhập bằng chuột, mà thay vào đó, hãy hỗ trợ cả hai cùng lúc.

Nếu bạn không sử dụng event.preventDefault() trong trình xử lý sự kiện chạm, một số sự kiện chuột được mô phỏng cũng sẽ được kích hoạt để đảm bảo hầu hết các trang web không được tối ưu hoá cho thao tác chạm vẫn hoạt động. Ví dụ: đối với một lần nhấn trên màn hình, các sự kiện này có thể được kích hoạt theo trình tự nhanh chóng và theo thứ tự sau:

  1. touchstart
  2. touchmove
  3. touchend
  4. di chuột qua
  5. mousemove
  6. mousedown
  7. nhả chuột
  8. click

Nếu bạn có các hoạt động tương tác phức tạp hơn một chút, thì các sự kiện chuột này có thể gây ra một số hành vi không mong muốn và làm hỏng quá trình triển khai của bạn. Tốt nhất là bạn nên sử dụng event.preventDefault() trong trình xử lý sự kiện chạm và quản lý dữ liệu đầu vào bằng chuột trong các trình xử lý sự kiện riêng biệt. Bạn cần lưu ý rằng việc sử dụng event.preventDefault() trong trình xử lý sự kiện chạm cũng sẽ ngăn một số hành vi mặc định, chẳng hạn như cuộn và sự kiện nhấp.

"Trong tính năng Tạo bằng Chrome, chúng tôi không muốn việc thu phóng xảy ra khi người dùng nhấn đúp vào trang web, mặc dù đó là tiêu chuẩn trong hầu hết các trình duyệt. Vì vậy, chúng ta sử dụng thẻ meta khung nhìn để yêu cầu trình duyệt không thu phóng khi người dùng nhấn đúp. Việc này cũng loại bỏ độ trễ nhấp 300 mili giây, giúp cải thiện khả năng phản hồi của trang web. (Độ trễ nhấp được dùng để phân biệt giữa thao tác nhấn một lần và nhấn đúp khi bật tính năng thu phóng bằng thao tác nhấn đúp.)

<meta name="viewport" content="width=device-width,user-scalable=no">

Hãy nhớ rằng khi sử dụng tính năng này, bạn phải đảm bảo trang web có thể đọc được trên mọi kích thước màn hình vì người dùng sẽ không thể phóng to.

Nhập bằng chuột, thao tác chạm và bàn phím

Trong chế độ Khám phá 3D, chúng tôi muốn có 3 cách để di chuyển trên bản đồ: chuột (kéo), cảm ứng (kéo, chụm để thu phóng và xoay) và bàn phím (di chuyển bằng các phím mũi tên). Tất cả các phương thức điều hướng này hoạt động hơi khác nhau, nhưng chúng tôi đã sử dụng cùng một phương pháp trên tất cả các phương thức đó; thiết lập các biến trong trình xử lý sự kiện và hành động trên biến đó trong vòng lặp requestAnimationFrame. Vòng lặp requestAnimationFrame không cần biết phương thức nào được dùng để điều hướng.

Ví dụ: chúng ta có thể thiết lập chuyển động của bản đồ (dragDX và dragDY) bằng cả ba phương thức nhập. Dưới đây là cách triển khai bàn phím:

document.addEventListener('keydown', onKeyDown );
document.addEventListener('keyup', onKeyUp );

function onKeyDown( event ) {
  input.keyCodes[ "k" + event.keyCode ] = true;
  input.shiftKey = event.shiftKey;
}

function onKeyUp( event ) {
  input.keyCodes[ "k" + event.keyCode ] = false;
  input.shiftKey = event.shiftKey;
}

//this needs to be called every frame before animation is executed
function handleKeyInput(){
  if(input.keyCodes.k37){
    input.dragDX = -5; //37 arrow left
  } else if(input.keyCodes.k39){
    input.dragDX = 5; //39 arrow right
  }
  if(input.keyCodes.k38){
    input.dragDY = -5; //38 arrow up
  } else if(input.keyCodes.k40){
    input.dragDY = 5; //40 arrow down
  }
}

function onAnimationFrame() {
  requestAnimationFrame( onAnimationFrame );
  //because keydown events are not fired every frame we need to process the keyboard state first
  handleKeyInput();
  //implement animations based on what is stored in input
   /*
  /
  */

  //because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
  input.dragDX = 0;
  input.dragDY = 0;
}

Ví dụ về nội dung nhúng: Sử dụng chuột, cảm ứng và bàn phím để điều hướng: http://cdpn.io/catlf

Tóm tắt

Việc điều chỉnh Build with Chrome để hỗ trợ các thiết bị cảm ứng có nhiều kích thước màn hình khác nhau là một trải nghiệm học tập. Nhóm chúng tôi không có nhiều kinh nghiệm trong việc tạo ra mức độ tương tác này trên thiết bị cảm ứng và chúng tôi đã học được rất nhiều trong quá trình này.

Thách thức lớn nhất hóa ra là làm thế nào để giải quyết trải nghiệm người dùng và thiết kế. Các thách thức kỹ thuật là quản lý nhiều kích thước màn hình, sự kiện chạm và các vấn đề về hiệu suất.

Mặc dù có một số thách thức với chương trình đổ bóng WebGL trên thiết bị cảm ứng, nhưng đây là một tính năng hoạt động tốt hơn dự kiến. Các thiết bị ngày càng trở nên mạnh mẽ hơn và việc triển khai WebGL cũng đang cải thiện nhanh chóng. Chúng tôi cảm thấy rằng chúng ta sẽ sử dụng WebGL trên các thiết bị nhiều hơn trong tương lai gần.

Bây giờ, nếu bạn chưa làm, hãy tạo một ứng dụng tuyệt vời!