- vừa được xem lúc

Responsive trong Three.js

0 0 19

Người đăng: Nguyễn Văn Huyên

Theo Viblo Asia

Cảnh chiếm toàn bộ viewport, responsive theo viewport

Trong các ví dụ trước, chúng ta đã tạo được một cảnh 3D đơn giản. Tuy nhiên, khi bạn thay đổi kích thước viewport của trình duyệt, bạn có thể thấy là kích thước của cảnh vẫn giữ nguyên. Do đó, nếu bạn mở rộng viewport thì sẽ có các khoảng trắng ở bên phải hoặc bên dưới, còn nếu bạn thu nhỏ viewport thì một số phần của cảnh sẽ bị che mất.

Mở rộng:

01-04-large

Thu nhỏ:

01-04-small

Để có thể thay đổi kích thước của cảnh theo kích thước của viewport, chúng ta cần lắng nghe sự kiện resize của window. Đây là JS bình thường, chưa phải là Three.js:

handleResize() { window.addEventListener('resize', () => { this.onResize(); });
}

Trong sự kiện resize, chúng ta sẽ cần update lại aspect của Camera theo tỷ lệ chiều ngang / chiều dọc của viewport, thông báo cập nhật lại ma trận chiếu của Camera, đồng thời cập nhật lại kích thước của Renderer:

onResize() { const width = window.innerWidth; const height = window.innerHeight; const aspect = width / height; this.camera.aspect = aspect; this.camera.updateProjectionMatrix(); this.renderer.setSize(width, height);
}

Chúng ta cũng cần lại render lại cảnh:

this.render();

Chú ý: Không xử lý nặng ở hàm resize. Có thể sử dụng kỹ thuật throttle để tránh hàm bị gọi với tần suất cao.

Toàn bộ code đầy đủ là (chapter-01/js/01-04.js):

import { Scene, PerspectiveCamera, WebGLRenderer, Color, BoxGeometry, MeshNormalMaterial, Mesh
} from 'https://unpkg.com/_@.com/build/three.module.js'; class ThreejsExample { constructor(canvas) { this.scene = this.createScene(); this.camera = this.createCamera(); this.renderer = this.createRenderer(canvas); const cube = this.createCube(); this.scene.add(cube); this.render(); this.handleResize(); } createScene() { const scene = new Scene(); return scene; } createCamera() { const aspect = window.innerWidth / window.innerHeight; const camera = new PerspectiveCamera(45, aspect, 0.1, 1000); camera.position.set(-30, 40, 30); camera.lookAt(this.scene.position); return camera; } createRenderer(canvas) { const renderer = new WebGLRenderer({ canvas, antialias: true }); renderer.setClearColor(new Color(0x000000)); const width = window.innerWidth; const height = window.innerHeight; renderer.setSize(width, height); return renderer; } createCube() { const cubeGeometry = new BoxGeometry(6, 6, 6); const cubeMaterial = new MeshNormalMaterial(); const cube = new Mesh(cubeGeometry, cubeMaterial); cube.position.set(-4, 3, 0); return cube; } render() { this.renderer.render(this.scene, this.camera); } handleResize() { window.addEventListener('resize', () => { this.onResize(); this.render(); }); } onResize() { const width = window.innerWidth; const height = window.innerHeight; const aspect = width / height; this.camera.aspect = aspect; this.camera.updateProjectionMatrix(); this.renderer.setSize(width, height); }
} new ThreejsExample(document.querySelector('#webglOutput'));

Ví dụ 01.04 - Responsive

Bạn có thể test responsive trên các thiết bị mobile bằng cách xoay qua lại giữa chế độ portraitlandscape. Sự kiện resize cũng xảy ra trên máy tính nếu như chúng ta sử dụng nhiều màn hình và di chuyển trình duyệt giữa các màn hình.

Cảnh không chiếm toàn bộ viewport, responsive theo canvas

Ở ví dụ trên, chúng ta đã xử lý trong trường hợp cảnh 3D chiếm toàn bộ viewport. Chúng ta tính toán với các kích thước của viewport (window.innerWidth, window.innerHeight). Vậy trong trường hợp cảnh không chiếm toàn bộ viewport thì sao? Ví dụ khi mà cảnh 3D chỉ là một phần trang trí của trang web, có thể có kích thước cố định nào đó theo pixel hoặc là kích thước tương đối theo phần trăm với viewport. Lúc đó chúng ta sẽ cần dựa vào phần tử canvas của trang, nơi mà cảnh 3D được render.

Để minh họa cho việc cảnh không chiếm toàn bộ viewport, chúng ta có thể để kích thước canvas chiếm 70% của chiều width và height:

<style> #webglOutput { width: 70%; height: 70%; }
</style>

Chúng ta sẽ cần chờ cho trang web được tải xong hoàn toàn rồi mới thực thi. Khi đó việc tính toán kích thước canvas mới chính xác. Chúng ta lấy kích thước canvas bằng các thuộc tính là canvas.clientWidthcanvas.clientHeight.

window.addEventListener('load', () => { new ThreejsExample(document.querySelector('#webglOutput'));
});

Lúc khởi tạo đối tượng Renderer (phương thức createRenderer), chúng ta sẽ thiết lập kích thước của Renderer bằng kích thước của canvas. Chú ý chúng ta thêm tham số thứ ba bằng false vào phương thức setSize. Khi truyền giá trị false, canvas sẽ không được thiết lập kích thước hiển thị bằng CSS với thuộc tính style. Three.js chỉ thiết lập thuộc tính widthheight của canvas.

const width = canvas.clientWidth;
const height = canvas.clientHeight;
renderer.setSize(width, height, false);

Nếu không truyền false:

Renderer setSize true

Nếu truyền false:

Renderer setSize false

Nếu chúng ta không truyền false thì những lần sau gọi canvas.clientWidth hoặc canvas.clientHeight sẽ luôn trả về một giá trị không thay đổi.

Ở phương thức onResize, chúng ta lấy lại đối tượng canvas thông qua thuộc tính domElement của đối tượng Renderer và xử lý theo kích thước của canvas. Khi gọi lại phương thức setSize của đối tượng Renderer, chúng ta cũng truyền tham số thứ ba là false.

const canvas = this.renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const aspect = width / height;
this.camera.aspect = aspect;
this.camera.updateProjectionMatrix();
this.renderer.setSize(width, height, false);

Toàn bộ code đầy đủ là (chapter-01/js/01-05.js):

import { Scene, PerspectiveCamera, WebGLRenderer, Color, BoxGeometry, MeshNormalMaterial, Mesh
} from 'https://unpkg.com/_@.com/build/three.module.js'; class ThreejsExample { constructor(canvas) { this.scene = this.createScene(); this.camera = this.createCamera(); this.renderer = this.createRenderer(canvas); const cube = this.createCube(); this.scene.add(cube); this.render(); this.handleResize(); } createScene() { const scene = new Scene(); return scene; } createCamera() { const aspect = window.innerWidth / window.innerHeight; const camera = new PerspectiveCamera(45, aspect, 0.1, 1000); camera.position.set(-30, 40, 30); camera.lookAt(this.scene.position); return camera; } createRenderer(canvas) { const renderer = new WebGLRenderer({ canvas, antialias: true }); renderer.setClearColor(new Color(0x000000)); const width = canvas.clientWidth; const height = canvas.clientHeight; renderer.setSize(width, height, false); return renderer; } createCube() { const cubeGeometry = new BoxGeometry(6, 6, 6); const cubeMaterial = new MeshNormalMaterial(); const cube = new Mesh(cubeGeometry, cubeMaterial); cube.position.set(-4, 3, 0); return cube; } render() { this.renderer.render(this.scene, this.camera); } handleResize() { window.addEventListener('resize', () => { this.onResize(); this.render(); }); } onResize() { const canvas = this.renderer.domElement; const width = canvas.clientWidth; const height = canvas.clientHeight; const aspect = width / height; this.camera.aspect = aspect; this.camera.updateProjectionMatrix(); this.renderer.setSize(width, height, false); }
} window.addEventListener('load', () => { new ThreejsExample(document.querySelector('#webglOutput'));
});

Ví dụ 01.05 - Responsive theo canvas

Pixel ratio

Trên màn hình máy tính bình thường thì pixel ratio thường bằng 1. Tuy nhiên, trên máy Mac hay thiết bị mobile, pixel ratio thường lớn hơn 1. Chúng ta có thể lấy pixel ratio của thiết bị hiển thị trong JS bằng thuộc tính window.devicePixelRatio.

Điện thoại của tôi có giá trị pixel ratio là 3. Cảnh 3D của ví dụ trước khi hiển thị trên điện thoại của tôi như sau:

pixel ratio 1 on mobile

Chúng ta có thể thấy rằng hình ảnh không được sắc nét lắm.

Để xử lý trên các thiết bị hiển thị mà có pixel ratio lớn hơn 1, khi thiết lập kích thước của Renderer, chúng ta hãy nhân với pixel ratio:

const pixelRatio = window.devicePixelRatio;
const width = canvas.clientWidth * pixelRatio;
const height = canvas.clientHeight * pixelRatio;
this.renderer.setSize(width, height, false);

Tất nhiên, việc tăng kích thước của Renderer sẽ tăng khối lượng tính toán, có thể ảnh hưởng đến hiệu năng. Chúng ta có thể xử lý chỉ để tối đa là 2 như sau:

const pixelRatio = Math.min(window.devicePixelRatio, 2);

Một số ví dụ trên mạng có thể hướng dẫn cách sau:

this.renderer.setPixelRatio(window.devicePixelRatio);

Tuy nhiên, documentation trên trang chủ của Three.js khuyên không nên sử dụng cách này.

Ví dụ 01.06 - Pixel ratio

Cảnh đã trông sắc nét hơn trên mobile:

pixel ratio 3 on mobile

Bình luận

Bài viết tương tự

- vừa được xem lúc

[ThreeJS] Xếp hình

Các bài trước trong series mình đã hướng dẫn mọi người tạo vài hình khối có sẵn rồi, hôm nay sẽ nâng cao hơn, ghép các khối cơ bản thành hình nhé. Khởi tạo PJ ThreeJS.

0 0 127

- vừa được xem lúc

Tạo cảnh 3D đầu tiên với Three.js

Giới thiệu. Các trình duyệt hiện đại hỗ trợ WebGL.

0 0 158

- vừa được xem lúc

Sử dụng dat.GUI để trải nghiệm dễ dàng hơn trong Three.js

Thư viện dat.GUI cho phép bạn tạo một giao diện đơn giản để bạn có thể thay đổi các biến trong code của bạn.

0 0 22

- vừa được xem lúc

Animation trong Three.js

Nếu chúng ta muốn animate cảnh, chúng ta cần render cảnh nhiều lần theo một tần suất nào đó. Các trình duyệt hiện đại có một giải pháp cho vấn đề, đó là hàm requestAnimationFrame.

0 0 29

- vừa được xem lúc

Các thành phần cơ bản tạo nên một cảnh 3D trong Three.js

Trong bài này, chúng ta sẽ cùng tìm hiểu về các thành phần như Scene, Camera, Renderer, Mesh, Geometry. Trước đó, chúng ta cũng tìm hiểu về hệ tọa độ trong Three.

0 0 102

- vừa được xem lúc

Camera Controls trong Three.js

Three.js có một số Camera Controls mà bạn có thể sử dụng để điều khiển Camera di chuyển bên trong cảnh.

0 0 55