GODRICH
#css-animation#button#loader#transition#scroll#ui#xu-huong-thiet-ke

9 hiệu ứng chuột máy tính bạn có thể copy-paste

Hiệu ứng chuột máy tính là tương tác nhỏ phản ứng theo cách con trỏ di chuyển, click, kéo hoặc cuộn.

Tự chạy · bấm ô để tới mục đó · cả 9 trong một zip

Chín hiệu ứng dưới đây không xếp theo độ phổ biến, mà theo đúng thứ tự con trỏ gặp trên một trang: đứng yên rồi đổi hình trên liên kết (01), bị hút về phía nút (02), ánh sáng và độ nghiêng lướt qua các thẻ (03·04), kéo màn hình sang ngang (05), bấm chuột phải để mở menu (06), tô màu câu vừa chọn (07), vòng tròn đầy dần khi đọc xuống trang (08), và cuối cùng là caption lướt qua lưới ảnh (09).

01Con trỏ đổi hình dạng

Bạn thấy gì

Di chuột vào liên kết, chấm con trỏ nhỏ phồng thành vòng tròn xanh có mũi tên bên trong; rời ra thì thu lại thành chấm. pointermove di chuyển vị trí chấm, còn pointerenter/pointerleave đổi hình dạng.

Con trỏ đổi hình dạng
Mở trong tab mới

01 · Con trỏ đổi hình dạng — Diện tích đo được 3.019% · khung hình chuyển động 16/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

pointermove translatedata-cursor 속성pointer:coarse 터치 제외

Dùng ở đâu

area.addEventListener('pointermove', function (e) {
  var r = area.getBoundingClientRect();
  cursor.style.transform =
    'translate(' + (e.clientX - r.left) + 'px,' +
    (e.clientY - r.top) + 'px)';
});
link.addEventListener('pointerenter', function () {
  cursor.classList.add('is-link');
});
link.addEventListener('pointerleave', function () {
  cursor.classList.remove('is-link');
});

02Nút hút nam châm

Chuyện gì diễn ra trên màn hình

Đưa con trỏ đến gần nút sẽ kéo nút hơi lệch về phía đó, rời xa thì bật trở lại vị trí cũ.

Nút hút nam châm
Mở trong tab mới

02 · Nút hút nam châm — Diện tích đo được 6.596% · khung hình chuyển động 11/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

pointermove 거리×계수transform: translatepointerleave 리셋

Chỗ dùng

Khoảng cách từ tâm vùng chạm được nhân với hệ số (0.4) để tính giá trị translate.

zone.addEventListener('pointermove', function (e) {
  var r = zone.getBoundingClientRect();
  var dx = e.clientX - (r.left + r.width / 2);
  var dy = e.clientY - (r.top + r.height / 2);
  btn.style.transform =
    'translate(' + dx * 0.4 + 'px,' + dy * 0.4 + 'px)';
});
zone.addEventListener('pointerleave', function () {
  btn.style.transform = '';
});

03Thẻ lộ sáng theo con trỏ

Chuyển động thế nào

Di chuột trên thẻ, một đốm sáng tròn di chuyển theo và làm sáng chữ mờ ở nơi ánh sáng lướt qua. mix-blend-mode: soft-light trộn ánh sáng vào nền, còn screen làm sáng chữ bên dưới.

Thẻ lộ sáng theo con trỏ
Mở trong tab mới

03 · Thẻ lộ sáng theo con trỏ — Diện tích đo được 20.166% · khung hình chuyển động 17/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

mix-blend-mode: soft-lightpointermove translatehover:none 대체

Dùng khi nào

card.addEventListener('pointermove', function (e) {
  var r = card.getBoundingClientRect();
  spot.style.transform =
    'translate(' + (e.clientX - r.left) + 'px,' +
    (e.clientY - r.top) + 'px)';
});
card.addEventListener('pointerleave', function () {
  spot.style.transform = '';
});

04Thẻ nghiêng 3D

Bạn thấy gì

Thẻ nghiêng theo không gian 3D tùy vị trí con trỏ, kèm ánh sáng chéo lướt qua.

Thẻ nghiêng 3D
Mở trong tab mới

04 · Thẻ nghiêng 3D — Diện tích đo được 3.769% · khung hình chuyển động 17/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

perspective + rotateX/rotateYbackground-position 스윕신뢰감 있는 밝은 톤

Dùng ở đâu

Vị trí con trỏ được đổi thành tỉ lệ 0–1, rồi thành góc rotateX/rotateY.

wrap.addEventListener('pointermove', function (e) {
  var r = wrap.getBoundingClientRect();
  var px = (e.clientX - r.left) / r.width;
  var py = (e.clientY - r.top) / r.height;
  var rx = (0.5 - py) * 6 * 2;
  var ry = (px - 0.5) * 6 * 2;
  card.style.transform =
    'perspective(600px) rotateX(' + rx + 'deg) rotateY(' + ry + 'deg)';
});

05Thư viện kéo để cuộn

Chuyện gì diễn ra trên màn hình

Kéo các ô như nắm bằng tay sẽ cuộn thư viện theo chiều ngang, thả ra thì dừng lại.

Thư viện kéo để cuộn
Mở trong tab mới

05 · Thư viện kéo để cuộn — Diện tích đo được 13.938% · khung hình chuyển động 20/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

scrollLeft 직접 조작overflow-x: auto 네이티브 터치cursor: grab/grabbing

Chỗ dùng

Đây là scrollLeft thật, không phải transform, nên cuộn bằng tay trên điện thoại vẫn giữ nguyên cảm giác cuộn gốc.

vp.addEventListener('pointerdown', function (e) {
  dragging = true;
  startX = e.clientX;
  startScroll = vp.scrollLeft;
  vp.setPointerCapture(e.pointerId);
});
vp.addEventListener('pointermove', function (e) {
  if (!dragging) return;
  vp.scrollLeft = startScroll - (e.clientX - startX);
});

06Menu chuột phải tùy chỉnh

Chuyển động thế nào

Bấm chuột phải vào vùng này sẽ làm menu biểu tượng sao chép/sửa/xóa bật lên tại điểm bấm, thay cho menu mặc định của trình duyệt.

Menu chuột phải tùy chỉnh
Mở trong tab mới

06 · Menu chuột phải tùy chỉnh — Diện tích đo được 13.451% · khung hình chuyển động 11/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

contextmenu preventDefault좌표 클램프Escape·바깥 클릭 닫기

Dùng khi nào

Toạ độ được giới hạn để menu không bao giờ tràn ra ngoài vùng.

pad.addEventListener('contextmenu', function (e) {
  e.preventDefault();
  var r = pad.getBoundingClientRect();
  var x = Math.min(e.clientX - r.left, r.width - 132);
  var y = Math.min(e.clientY - r.top, r.height - 112);
  menu.style.left = x + 'px';
  menu.style.top = y + 'px';
  pad.classList.add('is-open');
});

07Màu tô sáng khi chọn văn bản

Bạn thấy gì

Kéo chọn văn bản sẽ tô bằng màu highlighter đã chọn thay vì màu xanh mặc định, và huy hiệu sao chép hiện phía trên.

Màu tô sáng khi chọn văn bản
Mở trong tab mới

07 · Màu tô sáng khi chọn văn bản — Diện tích đo được 2.017% · khung hình chuyển động 15/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

::selection 색 지정selectionchange 이벤트getBoundingClientRect 배지 위치

Dùng ở đâu

Màu được đặt bằng đúng một dòng ::selection, vị trí huy hiệu lấy từ selectionchange.

::selection {
  background: #ff9ebb;
  color: #17141a;
}
document.addEventListener('selectionchange', function () {
  var sel = window.getSelection();
  if (sel.isCollapsed) return;
  var r = sel.getRangeAt(0).getBoundingClientRect();
  badge.style.left = r.left + r.width / 2 + 'px';
  badge.style.top = r.top - 28 + 'px';
  badge.classList.add('is-shown');
});

08Vòng con trỏ theo tiến độ cuộn

Chuyện gì diễn ra trên màn hình

Khi cuộn bên trong, vòng nhỏ theo con trỏ sẽ đầy dần theo tiến độ, đến cuối thì chuyển thành dấu tick. stroke-dashoffset của vòng giảm theo đúng tỉ lệ đã cuộn.

Vòng con trỏ theo tiến độ cuộn
Mở trong tab mới

08 · Vòng con trỏ theo tiến độ cuộn — Diện tích đo được 3.108% · khung hình chuyển động 22/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

stroke-dashoffsetscrollTop/scrollHeight 비율pointermove translate

Chỗ dùng

box.addEventListener('scroll', function () {
  var max = box.scrollHeight - box.clientHeight;
  var pct = max > 0 ? box.scrollTop / max : 0;
  ring.style.setProperty('--dash', (94.2 * (1 - pct)).toFixed(1));
  box.classList.toggle('is-done', pct > 0.98);
});

09Lưới ảnh hiện chú thích khi hover

Chuyển động thế nào

Di chuột vào ô lưới, dải chú thích trắng trượt lên từ dưới và hiện tên; focus bàn phím cũng phản ứng như vậy.

Lưới ảnh hiện chú thích khi hover
Mở trong tab mới

09 · Lưới ảnh hiện chú thích khi hover — Diện tích đo được 10.226% · khung hình chuyển động 21/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

:hover + :focus-visibletransform: translateYstagger animation-delay

Dùng khi nào

Không cần JS — chỉ :hover và :focus-visible.

.hg-tile__cap {
  transform: translateY(100%);
  transition: transform .3s ease-out;
}
.hg-tile:hover .hg-tile__cap,
.hg-tile:focus-visible .hg-tile__cap { transform: translateY(0); }

Hiệu ứng chuột máy tính dễ hỏng ở đâu?

Thẻ nghiêng 3D ở mục 04

Cái đầu tiên gặp phải là thẻ 04, thẻ nghiêng 3D. rotateX/rotateY chỉ nghiêng về mặt hình ảnh và không nên tính lại layout, nhưng ở độ rộng 320px, hộp giới hạn nhìn thấy của thẻ đã xoay vẫn bị tính vào vùng cuộn, khiến trang tràn sang ngang. Góc 9 độ ban đầu vẫn còn tràn; chỉ sau khi giảm xuống 6 độ và thu nhỏ thẻ lại thì bản render 320px mới hết tràn. Cái thứ hai là thẻ 05, thư viện kéo: giả lập việc kéo bằng ngón tay với transform sẽ lệch khỏi vị trí cuộn thật, nên khi kéo thật phải di chuyển trực tiếp scrollLeft. Cái thứ ba là menu chuột phải ở thẻ 06 — nếu không giới hạn toạ độ, menu sẽ bị cắt ra ngoài ở cạnh phải hoặc dưới của vùng.

Tiếp cận — khi con trỏ tùy chỉnh lại gây khó dùng

Phần tử bám theo con trỏ

Các thẻ 01, 03, 08 có phần tử bám theo con trỏ, nên toàn bộ transition/animation của chúng tắt khi prefers-reduced-motion: reduce. Nhưng vấn đề thật không phải là chuyển động mà là chính con trỏ — với người khiếm thị nhẹ, hoặc người dùng switch/joystick thay cho trackpad, một con trỏ tùy chỉnh nhỏ có thể khó thấy hơn con trỏ hệ thống. Vì vậy thẻ 01 ẩn hẳn điểm con trỏ bằng @media (hover: none), (pointer: coarse) trên thiết bị chạm, thẻ 08 thì trả lại con trỏ hệ thống trong cùng điều kiện đó. Thẻ 02, 04 vốn không có phần tử con trỏ riêng để ẩn — chỉ chuyển tiếp theo con trỏ bị tắt. Thẻ 03 thì làm sáng chữ ngay từ đầu thay vì ẩn spotlight. Thẻ 05, thư viện kéo, lại hoạt động tốt hơn trên cảm ứng vì cuộn gốc vẫn còn nguyên bên dưới. Thẻ 06, 07, 09 mỗi thẻ đều đi kèm một cách thay thế bằng bàn phím hoặc focus (Escape, Tab, :focus-visible) cho thao tác chỉ dành cho chuột (chuột phải, kéo chọn, hover), để vẫn dùng được khi không có chuột. Mật khẩu giải nén chín file này là c5gmgmdf, và sau khi giải nén, hai thư mục vanilla và react cho phép so sánh cùng chín hành vi đó cạnh nhau.

FAQ

Hiệu ứng chuột máy tính có thể làm chỉ bằng CSS không?

Một số thì có. Màu chọn ở thẻ 07 và lưới caption hover ở thẻ 09 chạy chỉ bằng ::selection và :hover. Số còn lại cần đọc toạ độ con trỏ, nên vài dòng JS thuần với pointermove là không tránh được.

Con trỏ tùy chỉnh có làm trang chậm đi không?

Chỉ đổi transform ở mỗi pointermove thì không cần tính lại layout, nên hầu hết vẫn mượt. Tuy vậy, với phần tử không đổi kích thước, nên lưu lại getBoundingClientRect một lần thay vì đo lại ở mỗi sự kiện.

Các hiệu ứng này có hoạt động trên di động không?

Thẻ 01, 02, 03, 04, 08 giả định có con trỏ chuột, nên trên cảm ứng chúng được ẩn hoặc chuyển sang trạng thái khác bằng CSS. Thư viện kéo ở thẻ 05 và các thẻ 06, 07, 09 được làm riêng để hoạt động tự nhiên trên cảm ứng.

Nguồn: MDN Pointer events, MDN prefers-reduced-motion · Xem thêm: Giới thiệu, Tương tác hover, Tương tác trang landing

Nhập mật khẩu file nén

Mật khẩu nằm ngay trong bài viết này. Đọc tới đó bạn sẽ thấy.