4 điểm bởi GN⁺ 2024-11-25 | 1 bình luận | Chia sẻ qua WhatsApp
  • Bắt đầu từ frosted acrylic UI của Forza Horizon 3 và Forza Motorsport 7, bài viết tổng hợp quá trình tái hiện hiệu ứng tấm kính tương tự trên web bằng HTML/CSS/JavaScript
  • Trọng tâm của hiệu ứng là Gaussian blur; cần dùng đồng thời CSS backdrop-filter-webkit-backdrop-filter cho iOS để giảm khác biệt giữa các trình duyệt
  • Chỉ làm mờ đơn thuần sẽ khiến bề mặt trông phẳng, nên dùng box-shadow: inset để thêm chiều sâu ở mép và subsurface scattering, đồng thời dùng bóng đổ thông thường để tạo cảm giác nổi trên nền
  • Phản chiếu bề mặt được triển khai bằng cách đặt ảnh tia sáng ở một lớp riêng; dùng background-attachment: fixed hoặc JavaScript để mô phỏng phản chiếu động theo chuyển động của kính
  • UI cuối cùng kết hợp các lớp .glass, .light, .drag-me, thêm bo góc, clipping và màu nền bán trong suốt để hoàn thiện thành một component kính có thể sao chép

Hiệu ứng tấm kính lấy từ UI game

  • Mục tiêu là tái hiện bằng HTML kiểu thiết kế frosted acrylic từng thấy qua kinh nghiệm phát triển UI của Forza Horizon 3 và Forza Motorsport 7
  • Thành phẩm là một tấm kính có thể kéo thả, bao gồm làm mờ nền, chiều sâu ở mép, phản chiếu ánh sáng, bo góc và lớp màu bán trong suốt
  • Phần đầu cung cấp công thức cuối cùng có thể sao chép dùng ngay, sau đó lần lượt xây dựng từng hiệu ứng theo từng bước

Tạo độ mờ nền bằng backdrop-filter

  • Cốt lõi của hiệu ứng frosted glass đẹp là Gaussian blur, làm mờ nền để trông như một màn chắn bán trong suốt
  • Trong CSS, hiệu ứng được triển khai bằng cách áp dụng blur cho backdrop-filter
  • Phần tử kính cơ bản bắt đầu từ một div đơn giản và class .glass
    • backdrop-filter: blur(10px)
    • -webkit-backdrop-filter: blur(10px)
  • Trên iOS, trừ khi thay đổi sâu các thiết lập riêng, vẫn cần -webkit-backdrop-filter
  • backdrop-filter chỉ mới được các trình duyệt hỗ trợ gần đây, người dùng Internet Explorer sẽ không thể trải nghiệm demo

Tạo chiều sâu bằng mép và bóng đổ

  • Nếu chỉ áp dụng backdrop-filter: blur(10px), kính có thể trông phẳng và đơn điệu
  • Mép kính được triển khai bằng box-shadow: inset thay vì border: 1px solid
    • border ảnh hưởng đến kích thước phần tử và cách bố trí phần tử con
    • Khi dùng box-shadow: inset, phần tử con có thể lấp đầy vùng nội dung của kính một cách tự nhiên mà không cần các điều chỉnh như margin âm
  • Hai inset shadow màu trắng bán trong suốt được dùng để thể hiện mép kính
    • Shadow thứ nhất biểu thị mép kính nhìn thấy từ bên ngoài; trong ví dụ xuất hiện ở mép dưới và mép trái
    • Shadow thứ hai biểu thị mép nhìn thấy xuyên qua kính; trong ví dụ xuất hiện ở mép trên và mép phải
  • Mép nhìn thấy xuyên qua kính nhận ít ánh sáng hơn, nên được đặt mềm và trong suốt hơn
  • Thêm box-shadow tối thông thường có thể tạo cảm giác kính nổi lên một cách vật lý khỏi nền

Tán xạ ánh sáng và phản chiếu bề mặt

  • Cảm giác ánh sáng lan bên trong kính được biểu đạt bằng cách đơn giản hóa subsurface scattering
  • Việc triển khai tiếp tục dùng box-shadow: inset
    • Hiệu ứng tán xạ ngắn: inset 0px 0px 10px 5px rgba(255, 255, 255, 0.025)
    • Hiệu ứng tán xạ sâu hơn: inset 0px 0px 40px 5px rgba(255, 255, 255, 0.025)
  • Hiệu ứng này dễ thấy hơn khi kéo kính trên nền tối
  • Ngoài cảm giác nền bị làm mờ và khúc xạ, còn thêm đặc tính phản chiếu ánh sáng trên bề mặt kính
  • Phản chiếu ánh sáng được triển khai bằng cách trộn ảnh tia sáng vào nền
    • hướng dẫn YouTubephần giải thích bằng văn bản có thể tham khảo khi tự tạo ảnh tương tự
    • Nếu đưa ảnh trực tiếp vào background-image của phần tử .glass, backdrop-filter sẽ làm mờ ảnh đó, vì vậy cần dùng một lớp riêng như ::before

Biến phản chiếu tĩnh thành động

  • Chỉ đặt ảnh tia sáng lên sẽ tạo được phản chiếu, nhưng khi di chuyển kính, ánh sáng có thể trông như bị cố định trên bề mặt kính
  • Cách dùng CSS thuần là thêm background-attachment: fixed vào ::before
    • Cách triển khai này đơn giản nhưng có thể không hoạt động trên mọi nền tảng
  • Để hoạt động trên mọi nền tảng, dùng JavaScript để mô phỏng background-attachment: fixed
    • Việc thao tác trực tiếp với ::before bằng JavaScript không hiệu quả, nên dùng phần tử div.light riêng
    • Gắn thuộc tính data-js-background-attachment-fixed vào phần tử mục tiêu
  • JavaScript đọc giá trị getBoundingClientRect() của phần tử mục tiêu ở mỗi frame animation, rồi cập nhật background-position theo hướng ngược lại với vị trí trong viewport
    • backgroundPositionX = -clientRect.x
    • backgroundPositionY = -clientRect.y
  • Vòng lặp lặp lại được chạy bằng requestAnimationFrame

Cấu trúc HTML/CSS cuối cùng

  • HTML cuối cùng của phiên bản JavaScript có cấu trúc đặt .light dùng cho phản chiếu ánh sáng và .drag-me là lớp văn bản/màu bên trong .glass

  Drag Me

  • .glass phụ trách hình dạng cơ bản và hiệu ứng thị giác của tấm kính
    • Áp dụng làm mờ nền bằng backdrop-filter-webkit-backdrop-filter
    • Áp dụng mép, bóng đổ và hiệu ứng tán xạ bằng nhiều box-shadow
    • Dùng position: relative để các lớp con có thể lấp đầy vùng của phần tử cha
    • Áp dụng bo góc bằng border-radius: 5px
    • Dùng overflow: hidden để cắt các phần tử con không tràn ra ngoài góc bo
  • .light dùng ảnh tia sáng làm nền lặp lại
    • background-image: url(path/to/light.png)
    • background-repeat: repeat
    • background-size: 750px
    • opacity: 0.075
    • Lấp đầy toàn bộ phần tử cha bằng position: absolute và offset 0 ở bốn hướng
    • Render phía sau các phần tử con khác bằng z-index: -1
  • .drag-me căn giữa nội dung và tạo lớp kính bán trong suốt có màu
    • display: flex
    • align-items: center
    • justify-content: center
    • height: 30px
    • background-color: rgba(12, 13, 14, 0.75)

Khác biệt của phiên bản không dùng JavaScript

  • Phiên bản không dùng JavaScript sử dụng .glass::before thay cho phần tử .light riêng
  • Áp dụng ảnh tia sáng vào ::before và dùng background-attachment: fixed để tạo phản chiếu cố định theo màn hình
  • Cách này không hoạt động trên mọi nền tảng; phiên bản JavaScript bù lại bằng cách cập nhật trực tiếp background-position

1 bình luận

 
GN⁺ 2024-11-25
Các ý kiến trên Hacker News
  • Về mặt thẩm mỹ thì tôi thật sự rất thích, nhưng ở phần rìa có vài chỗ không khớp về mặt vật lý
    Từ góc nhìn của một lập trình viên embedded, tôi thấy nó hơi lãng phí. Chẳng phải mỗi client đều phải tính một bộ lọc blur “đắt đỏ” mỗi lần để trộn với phiên bản bị làm mờ của ảnh nền sao?
    Có dùng GPU và được tối ưu nên rốt cuộc có thể không tốn kém lắm, nhưng phát triển web ngày nay nhìn chung có vẻ đang đi theo hướng quá cồng kềnh. Ngay cả các site đơn giản cũng tải mất 5 giây, rồi đẩy các xử lý nặng sang phía client; dĩ nhiên đây không phải lỗi của tác giả bài viết này

    • Có lẽ mỗi người đều có định kiến riêng về việc thế nào là lãng phí
      Tôi lớn lên thời modem 14.4k, nên đã quen nghĩ rằng băng thông mạng khan hiếm và đắt đỏ hơn thời gian CPU rất nhiều
      Nếu có thể dễ dàng tính toán ở client, thì tôi cho rằng tải cả ảnh qua Internet còn lãng phí hơn. Để nhận bức ảnh đó, router, server, cả ổ đĩa ở xa đều phải hoạt động; dùng tất cả những thứ đó chỉ để tránh xử lý một lần trên dữ liệu đã nằm sẵn trong RAM của client thì còn đáng tiếc hơn
    • Những website về mặt kỹ thuật có vẻ “lãng phí”, nếu nhìn vào công dụng thực tế thì ở tầm rộng hơn phần lớn được dùng cho giải trí thuần túy
      Vì vậy hoặc bản thân giải trí là lãng phí, hoặc nếu không thì việc dùng thêm tính toán để làm giải trí tốt hơn cũng ổn
    • Với con người, nó trông như “tính toán lại đáng kể đồ họa đã render”, nên cảm giác lãng phí, nhưng về kỹ thuật thì tôi nghĩ không hẳn vậy
      Muốn vẽ lại những gì thay đổi trong UI thì dù sao cũng cần tính toán GPU, và một hiệu ứng blur đơn giản có thể được thêm vào khá hiệu quả. Chi phí có khả năng còn thấp hơn animation các đối tượng DOM không được tối ưu thành layer GPU
      Nhìn việc ngay cả những site đơn giản nhất hiện nay cũng tải hơn 1MB JavaScript cùng cả đống tracker và ngốn CPU, thì blur cho hiệu ứng thẩm mỹ nằm khá xa phía dưới trong danh sách lãng phí
    • Tôi nghĩ cách gửi cả ảnh đã blur lẫn ảnh gốc qua mạng chẳng phải là cách phát triển web đã làm suốt 20 năm qua sao
      Nếu có nhiều ảnh độ phân giải cao, băng thông mạng thực tế là hữu hạn, nhất là trong môi trường mobile có vùng phủ sóng không dây kém, thì chắc chắn hiệu năng sẽ tệ hơn so với tính toán cục bộ. Ngày nay CSS/WebGL làm được những việc thật đáng kinh ngạc, còn trước đây để tạo các hiệu ứng như thế này cần rất nhiều hack và đường vòng
    • Tôi nghĩ đã đến lúc nghiêm túc chấp nhận rằng khá nhiều công nghệ kiểu “ồ trông hay đấy, chắc chắn tôi/chúng ta cần nó” không có chỗ đứng trên đời này
      Bất kể bản thân hành vi đó có lãng phí hay không, chính lối tư duy này cho thấy một vấn đề lớn hơn của xã hội. Vì sao ta cần cái này, và vì sao lại xem trạng thái không có nó là tệ?
      Nghĩ đến quy mô các vấn đề xã hội ngày nay, ai còn quan tâm panel UI có bị làm mờ hay không cơ chứ
  • Khi thử xem có thể làm gì với shader API mới trên Android, tôi từng tái hiện hiệu ứng Windows 7 Aero Glass và trích xuất một texture “vệt sáng” tương tự
    Kết quả rất giống bài viết này, chỉ là tôi không thêm bóng đổ bên trong và tăng độ bão hòa của nền bị làm mờ lên một chút
    https://mastodon.social/@grishka/110524476584503201

    • Rất tuyệt. Texture vệt sáng thực sự có thể đóng vai trò lớn trong hiệu ứng này
      Nói thật, thứ làm cho tutorial cũng không xuất sắc đến thế. Tôi thích các đường nét sắc hơn của phía Windows 7
      Tôi cũng đã nghĩ đến việc chỉnh độ bão hòa và thấy trong các ví dụ khác, nhưng quyết định không dùng trong bài. Ảnh mặt trăng được dùng không có nhiều màu nên có lẽ cũng không khác biệt lớn. Những ai tự tạo hiệu ứng kính nên thử nghiệm cả độ bão hòa
  • Bản thân phần thực hiện rất xuất sắc, và cách trình bày bằng demo tương tác cũng hay. Kéo cửa sổ lên trên mặt trăng khá thú vị
    Tuy nhiên tôi không thích dùng hiệu ứng kính mờ trong thiết kế giao diện người dùng, và khi nó được thêm vào Mac OS thì tôi cảm thấy đó là một bước lùi. Các designer quá dễ bị hiệu ứng này mê hoặc, nhưng thực tế nó không bao giờ lý tưởng. Đặc biệt là khi nội dung phía sau panel là động hoặc do người dùng tạo
    Nó giống như brushed metal của thập niên 2010, và thật đáng ngạc nhiên là nó đã rò rỉ từ Windows Vista rồi lan ra khắp nơi

    • Tôi không hiểu vì sao lại “không lý tưởng”. Thiết kế thị giác phần lớn phụ thuộc vào sở thích cá nhân về cái gì trông đẹp, hơn là mục đích thực dụng
      Tất nhiên thiết kế cũng ảnh hưởng đến chức năng, nhưng mục đích chính là làm trải nghiệm người dùng thú vị hơn
      Tôi cho rằng skeuomorphism làm giao diện thú vị hơn xu hướng tối giản làm phẳng mọi thứ suốt hơn 10 năm qua. Trang trí thị giác mô phỏng bề mặt và chất liệu trong thế giới thực đem lại sự quen thuộc và dễ tiếp cận. Về mặt thực dụng, flat design khiến khó phân biệt các phần tử và trạng thái, trong khi thời còn nút 3D, brushed metal, kính mờ thì mọi thứ rõ ràng hơn nhiều
      Tôi nghĩ giờ con lắc đang quay trở lại. Khi ngày càng nhiều designer tránh vẻ ngoài phẳng, tôi hy vọng UI thân thiện với con người hơn sẽ hồi sinh
    • Tôi không phải UI designer nên muốn hỏi nghiêm túc: vấn đề là bản thân việc dùng các hiệu ứng thẩm mỹ như kính mờ hay brushed metal trong thiết kế UI, hay là khi cố tạo những giao diện như vậy, một số designer đã đánh mất các tín hiệu thị giác cho biết vị trí và vai trò của control?
    • Tôi đã đặt một chút độ trong suốt cho cửa sổ terminal suốt nhiều năm, nhưng giờ chuyện này làm tôi phải nghĩ lại
    • Brushed metal không ảnh hưởng đến ngữ cảnh thông tin. Blur cung cấp ngữ cảnh có thể điều chỉnh
      Blur thường không phải đáp án đúng, nhưng đôi khi lại là lựa chọn phù hợp
    • Trớ trêu là các phiên bản macOS trước đây cũng dùng rất nhiều brushed metal
  • Năm 2008, trước khi CSS hỗ trợ backdrop-blur, từng có một thử nghiệm CSS tạo hiệu ứng tương tự: https://webdev.andersriggelsen.dk/aero/
    Cách làm là áp hiệu ứng kính lên một ảnh nền đã được làm mờ sẵn và đặt ở vị trí cố định: https://webdev.andersriggelsen.dk/aero/bgl.jpg
    Hiệu năng tốt hơn nhiều so với Gaussian blur theo thời gian thực, nhưng vẫn có nhược điểm là không cho phép ảnh nền động

    • Thậm chí còn từng tiến xa hơn sớm hơn nữa bằng cách dùng một file Flash làm nền
      Khi đó vẫn có thể tạo hiệu ứng kính ngay cả trong một khung có thể chuyển động động. Tôi không nhớ có dùng iframe hay không nên phải tìm lại mã, nhưng xét thời điểm đó thì có lẽ khả năng cao là có dùng
    • Cửa sổ nổi không được cập nhật sau vị trí ban đầu
      Tôi đang dùng Windows 10 19045.5131, còn Chrome là bản mới nhất Version 131.0.6778.86 (Official Build) (64-bit)
  • Thành tựu kỹ thuật thì ấn tượng, nhưng tôi xem đây là UX tệ
    Nội dung trong các panel bán trong suốt khó đọc hoặc khó hiểu hơn so với trên nền một màu nhất quán; độ tương phản với nền thấp và không đều. Hơn nữa, nếu có thứ gì đó chuyển động bên dưới thì có thể gây xao nhãng và làm mất phương hướng

    • Khó có thể khái quát một hiệu ứng thị giác đơn giản là “UX tệ”. Nó phụ thuộc rất nhiều vào cách tích hợp
      Nếu tích hợp tốt, cần điều chỉnh cường độ hiệu ứng để UI không bị quá đà, kiểm tra hỗ trợ tính năng để tránh thêm trang trí khi không hỗ trợ blur, và kiểm tra khả năng hiển thị/video để không render trên những thiết bị có khả năng GPU không đủ
      Ngoài ra, cần kiểm tra tùy chọn của người dùng để cung cấp nền một màu có độ tương phản tốt cho những người gặp khó với nền phức tạp, giới hạn phạm vi hiệu ứng và bóng đổ theo không gian khả dụng, đồng thời điều chỉnh blur và độ mờ đục dựa trên độ phức tạp và độ tương phản của nền bên dưới vùng mờ
      Cũng cần bảo đảm blur và bóng đổ co giãn thích ứng theo typography. Về bản chất, UX là thiết kế cách người dùng trải nghiệm tương tác với toàn bộ ứng dụng, chứ không phải thành bại chỉ vì một lựa chọn phong cách riêng lẻ
    • Windows Vista đã giải quyết vấn đề này bằng cách thêm viền giống bóng trắng cho chữ đen trên kính mờ
      Điều đó bảo đảm khả năng đọc của văn bản ngay cả trong trường hợp cực đoan khi nền màu đen. Trong CSS có thể tái tạo bằng thuộc tính text-shadow
    • Vì vậy tôi không dùng hiệu ứng này trên toàn bộ trang của mình. Bởi vì tạo UX tốt sẽ khó hơn
      Tuy nhiên trong game thì có nhiều chỗ dùng hơn. Đặc biệt nó có thể phù hợp với HUD, nơi không muốn che nền là vùng chính mà người dùng tương tác
    • Nếu vấn đề là độ tương phản thấp, chỉ cần làm nền hoặc sắc kính tối hơn và nội dung tiền cảnh sáng hơn, hoặc ngược lại
      Độ tương phản tốt là một thách thức với bất kỳ yếu tố thiết kế nào, dù là nền một màu hay hiệu ứng kính
      Việc những gì diễn ra ở nền gây xao nhãng và làm mất phương hướng cũng rất chủ quan. Cũng như giảm độ mờ đục của một màu đặc, ta có thể giảm độ bán trong suốt của kính
      Khó có thể xem phê bình này là đúng một cách khách quan, và vẫn có cách tích hợp yếu tố thiết kế này trong khi tránh các vấn đề đó. Ảnh chụp màn hình Forza trông ổn, và khi nhìn thực tế thì nền cũng không gây xao nhãng. Đổi lại, ta có một UI thú vị và dễ chịu về mặt thị giác hơn so với nền một màu. Điều này có thể quan trọng hơn trong video game, nhưng cũng có thể dùng tinh tế trong thiết kế OS và web
    • Blur là cách che giấu thông tin trong khi vẫn giữ lại ngữ cảnh ở mức có thể điều chỉnh
      Nếu cần nhiều ngữ cảnh thì blur không phải lựa chọn tốt; nếu hoàn toàn không cần ngữ cảnh thì cũng không phải lựa chọn tốt
      Nhưng đôi khi nó là một lựa chọn tuyệt vời
  • Ví dụ tương tác thì hay, nhưng nếu có toggle để so sánh song song với bước trước hoặc chuyển qua lại thì có lẽ còn tốt hơn
    Ở hầu hết các bước, cải tiến quá tinh tế nên khó nhận ra khác biệt

    • Đồng ý. Trên di động, sau bước thứ hai thì gần như không thấy khác biệt hoặc hoàn toàn không nhận ra
  • Có khoảng 10 hiệu ứng chồng lên nhau, và nếu không nhắm tới thẩm mỹ cực kỳ cầu kỳ thì khoảng 7 cái là quá nhiều
    Chỉ cần màu nền, blur, bóng đổ hoặc viền là đủ

    • Những ai muốn hiệu ứng kính nhẹ hơn có thể chọn phần mình cần từ tutorial
      Chỉ đến bước thứ ba thôi, với blur và bóng đổ, đã có một mảnh kính khá ổn. Cá nhân tôi khuyên nên thêm một ảnh vệt sáng tĩnh vào đó. Tôi nghĩ hầu hết ứng dụng không cần toàn bộ hiệu ứng JavaScript. Nó vui mắt, nhưng có thể gây xao nhãng
      Chỉ với blur, bóng đổ và vệt sáng, cũng có thể tiến rất gần tới ảnh Forza ở đầu bài
    • Thật thú vị là đa số bình luận phía trên đều nói về sự lãng phí
      Tôi nghĩ đó là một trong những lý do mọi người chuyển sang flat design. Windows Vista và hiệu ứng kính rất đẹp, nhưng chi phí tính toán lớn
      Flat design gây tranh cãi về mặt thẩm mỹ, và đặc biệt với người dùng lớn tuổi, nó có thể khó dùng hơn thiết kế skeuomorphic [0][1]
      Nếu tính hiện thực có thể hỗ trợ khả năng sử dụng, thì việc dùng những hiệu ứng như thế này trên web là hoàn toàn hợp lý
      [0]: https://www.tandfonline.com/doi/abs/10.1080/0144929X.2020.18...
      [1]: https://www.sciencedirect.com/science/article/abs/pii/S01419...
    • Tôi tò mò vì sao lại xem giới hạn của các hiệu ứng chồng lên nhau là n <= 3
      Không rõ đó là vấn đề về khả năng tiếp cận, hiệu năng, bảo trì, hay lý do nào khác
      Trong các năng lực phát triển của tôi, CSS là phần yếu nhất áp đảo, nên tôi hoàn toàn không biết đâu là thực hành tốt và đâu là thực hành xấu
    • Có phải bạn đã bỏ lỡ đoạn tác giả giải thích rằng họ đang nhắm tới thẩm mỹ cầu kỳ nhất không?
  • Thật thú vị vì có cảm giác mọi thứ đã đi hết một vòng
    Một trong những demo CSS đầu tiên, demo cho CSS 1, là hiệu ứng kính mờ: https://meyerweb.com/eric/css/edge/complexspiral/glassy.html

  • Thành thật mà nói, khá ấn tượng. Lúc nào cũng thú vị khi thấy CSS có thể sáng tạo và mạnh mẽ đến mức nào
    Tuy nhiên, mình cảm giác trong HTML/CSS có một tầng nào đó mà mình sẽ mãi không hiểu nổi. Demo này đúng là như vậy: người này đang tạo ra một ô cửa kính phủ sương, còn mình thì vẫn chưa căn giữa nổi một cái div

    • Hồi đang học cách web development vận hành, mình đã phải trả giá mới hiểu rằng thêm một div nữa thường là ý hay
      Nhìn hầu hết website hiện đại, div được chất thành những đống khổng lồ. Nếu muốn một div được căn giữa, thì tạo thêm một div chiếm toàn bộ không gian để căn giữa bên trong nó sẽ tương đối dễ hơn
      Nhiều thủ thuật thị giác cũng tương tự, nhưng ví dụ này rất gọn gàng nên càng ấn tượng hơn
  • Không hẳn là kính phủ sương, nhưng mình đã thử thêm hiệu ứng kính vào sidebar của https://www.gyan.dev/ffmpeg/builds/

    • Xem trên PC thì trang này rất dễ chịu và đẹp