11/08/2018, 21:24

Implement Tam giác ma thuật của Amazon

Tuần này mình tham gia vào nhóm hardcore, là một nhóm nghiên cứu có mục tiêu là tìm hiểu chuyên sâu về các vấn đề công nghệ, các bạn quan tâm có thể tham khảo rule của nhóm tại đây. Mỗi tuần các thành viên sẽ pick một chủ đề và cuối tuần phải có được một bài viết tổng hợp lại những gì mình tìm hiểu ...

Tuần này mình tham gia vào nhóm hardcore, là một nhóm nghiên cứu có mục tiêu là tìm hiểu chuyên sâu về các vấn đề công nghệ, các bạn quan tâm có thể tham khảo rule của nhóm tại đây. Mỗi tuần các thành viên sẽ pick một chủ đề và cuối tuần phải có được một bài viết tổng hợp lại những gì mình tìm hiểu được về chủ đề đó.

Tuần này mình pick chủ đề về UI: Các kĩ thuật implement Tam giác ma thuật của Amazon

Tam giác ma thuật?

Nếu lần đầu tiên nghe nói đến khái niệm này thì các bạn có thể tìm đọc bài viết của a Vũ Nhật Minh.

Mình tóm tắt sơ:

Vùng màu xanh đậm được gọi là vùng "tam giác ma thuật" (chống chỉ định đối với các bạn có đầu óc đen tối :trollface:), mục đích của nó là giúp cho thao tác move chuột từ menu bên trái sang menu con bên phải được mượt mà hơn. Không bị dính vấn đề mất focus mà ẩn luôn cả cái menu, ví dụ:

Implement nó như thế nào?

Tính tới thời điểm này thì đã có vô số thư viện hỗ trợ chúng ta thực hiện chức năng này, nhưng mục đích của bài viết này là đào sâu và tìm hiểu tại sao người ta lại implement được nó, và implement bằng cách nào.

Ở đây mình sẽ trình bày 2 phương pháp, một phương pháp thủ công và một phương pháp khác chuyên sâu hơn, có đụng tới một tí toán :D

Phương pháp thủ công: Dùng thẻ SVG

Đây là cách giải quyết đơn giản nhất, không đòi hỏi kiến thức gì chuyên sâu, và cũng là một cách "thô bạo" nhất :))

alt text

<div id="menu-item">
  <div class="item">Mouse on me</div>
  <div class="content hover">Come to me!</div>
</div>

Ví dụ chúng ta có một menu như hình bên trên, Yêu cầu là khi rê chuột vào vùng Mouse on me thì sẽ hiện ra vùng Come to me đúng theo đường đi của tam giác ma thuật.

Cách giải quyết khá đơn giản, đó là tạo một thẻ SVG để xác định vùng di chuyển của chuột, mà nếu con trỏ chuột đi trên đó thì menu sẽ không bị mất đi. Vùng đó sẽ bao gồm một vùng tam giác nối từ Mouse on me đến Come to me và cả vùng Come to me luôn.

alt text

<div id="menu-item">
  <div class="item">Mouse on me</div>
  <div class="content hover">Come to me!</div>
  <svg>
    <polygon id="magic-triangle-menu-1" points="75,25 150,0 400,0 400,200 150,200" style="fill:lime;stroke:purple;stroke-awidth:0" />
  </svg>
</div>

Chúng ta định nghĩa vùng di chuyển bằng một thẻ SVG như bên trên, tạo ra một đa giác (polygon) cấu thành từ các điểm (75:25), (150:0), (400:0), (400:200), (150:200), các điểm này chính là các đỉnh của tam giác và đỉnh của sub menu Come to me.

Công việc còn lại chắc các bạn cũng đã đoán ra được, đó là kiểm tra nếu trỏ chuột đang di chuyển trên vùng đa giác này thì hiện menu ra.

Ưu điểm của cách này là không cần phải tính toán rườm rà, có thể "hard code" khi tạo menu. Nhược điểm là việc "hard code" và việc tạo ra quá nhiều thẻ SVG trên trang web sẽ làm ảnh hưởng tới performance và dẫn tới việc cấu trúc HTML không đẹp, ảnh hưởng SEO này nọ...

Phương pháp động: Kiểm tra tọa độ chuột khi di chuyển

Cũng tương tự như phương pháp ở trên, nhưng ở cách implement này chúng ta không cần thiết phải tạo ra vùng đa giác một cách thủ công nữa mà sẽ kết hợp một vài phép toán để kiểm tra tọa độ chuột có nằm trong vùng đa giác này hay không.

Chúng ta có thể lấy được tọa độ chuột, chúng ta cũng có thể xác định được mỗi đỉnh trong đa giác cần kiểm tra (vì đã có kích thước và tọa độ cụ thể của các menu).

Vậy vấn đề ở đây là: Làm sao để biết được con trỏ chuột có đang nằm trong một đa giác hay không?

Đa giác trên được cấu thành từ một tam giác và một hình chữ nhật:

alt text

Vậy bài toán của chúng ta ở đây có thể tách thành 2 bài toán con:

  • Kiểm tra một điểm nằm trong một tam giác
  • Kiểm tra một điểm nằm trong một hình chữ nhật

Đến đây thì vấn đề đã trở nên khá là dễ rồi, chúng ta chỉ cần dùng phương pháp diện tích để kiểm tra một điểm (ở đây chính là tọa độ chuột) có nằm trong tam giác hoặc hình chữ nhật hay không.

Một điểm nằm trong một tam giác

alt text

Để một điểm D nằm trong tam giác ABC thì diện tích của tam giác ABC sẽ bằng tổng diện tích của các tam giác ADB, BDCCDA:

alt text

Để tính diện tích của một tam giác khi biết tọa độ 3 đỉnh của nó, chúng ta có thể dùng công thức Shoelace (thank bạn @TanDuong đã sửa lại cho mình chỗ này :D)

alt text

Nhìn đống biểu thức toán có vẻ sợ sợ :v thôi thì đây là đoạn code diễn đạt công thức này:

const area = function(A, B, C) {
  return Math.abs(( A.x * (B.y - C.y) + B.x * (C.y - A.y) + C.x * (A.y - B.y) ) / 2);
}

Và đây là hàm kiểm tra một điểm nằm trong một tam giác bằng phương pháp tính tổng diện tích 3 tam giác đã nói ở trên:

const pointInTriangle = function(D, A, B, C) {
  let ABD = area(A, B, D);
  let BDC = area(B, D, C);
  let CAD = area(C, A, D);
  let ABC = area(A, B, C);
  if (ABC == (ABD + BDC + CAD)) {
    return true;
  }
  return false;
}

Ngoài phương pháp dùng diện tích, còn có rất nhiều phương pháp khác, các bạn có thể xem thread thảo luận này để tìm hiểu về ưu nhược điểm của từng loại phương pháp: http://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle

Một điểm nằm trong một hình chữ nhật

Vẫn bằng phương pháp diện tích:

alt text

Một điểm E sẽ nằm trong hình chữ nhật ABCD nếu tổng diện tích các tam giác AEB, BEC, CEDDEA bằng diện tích hình chữ nhật ABCD.

alt text

Và để tính diện tích mỗi tam giác thì chúng ta đã có công thức như bên trên, còn tính diện tích hình chữ nhật ABCD thì có cần phải ghi công thức luôn không nhỉ? :trollface:

alt text

Code thì có lẽ không cần phải ghi ra làm gì nữa :))

Giống như trường hợp kiểm tra tam giác, với hình chữ nhật, ngoài phương pháp diện tích chúng tra còn có rất nhiều phương pháp khác, có thể tham khảo thêm tại đây: http://math.stackexchange.com/questions/190111/how-to-check-if-a-point-is-inside-a-rectangle

Sau khi đã kiểm tra được con trỏ chuột có nằm trong vùng tam giác hay vùng hình chữ nhật không, thì việc tiếp theo là implement hoòa chỉnh hiệu ứng tam giác ma thuật có lẽ không khó nữa :D

Dưới đây là implement của bạn @HoSyCanh, có cải tiến thêm cách kiểm tra hướng di chuyển của con trỏ chuột để giúp menu hiển thị thông minh hơn. Vùng tam giác được bạn ấy thêm vào để minh họa:

0