react-krpano-toolkit
Version:
A React toolkit for KRPano integration with reusable functions and screen/hotspot configuration management
1,781 lines (1,462 loc) • 51.4 kB
Markdown
//github.com/vuvandinh203) - vuvandinh.work@gmail.com - Website : [vuvandinh.id.vn](https://vuvandinh.id.vn)
Từ npm:
```
npm install react-krpano-toolkit
```
Từ yarn:
```
yarn add react-krpano-toolkit
```
1. [Scene Operations](
2. [View Operations](
3. [Element Operations](
4. [Sound Operations](
5. [Control Operations](
6. [Utility Operations](
7. [Base Execute Function](
8. [Shortcuts](
9. [Kết hợp nhiều thao tác](
---
---
```
public/
└── krpano/
├── tour.xml
├── tour.js
├── tour.swf
└── tiles/...
src/
└── App.js
└── main.js
```
react-krpano-toolkit là thư viện React mạnh mẽ giúp bạn tích hợp Krpano Panorama Viewer vào ứng dụng React một cách dễ dàng và linh hoạt. Thư viện cung cấp cả API core lẫn event system, hỗ trợ tương tác 2 chiều giữa React và Krpano.
| Hook | Nhóm | Chức năng / Mô tả |
| ----------------------- | ---- | --------------------------------------------------------------------------------------------- |
| `useKrpano` | Core | Tổng hợp Sự kiện sẽ gửi đi đến `krpano` . |
| `useKrpanoGlobalAction` | Core | Thực hiện các hành động global trên Krpano, như load scene, load panorama, reset view. |
| `useControl` | Core | Quản lý các control (buttons, UI) trong panorama, thao tác show/hide, enable/disable. |
| `useElement` | Core | Quản lý các element trong panorama như layers, hotspots, plugin, hỗ trợ thêm/xóa/sửa element. |
| `useExecute` | Core | Gọi trực tiếp các lệnh Krpano (`execute`) với chuỗi lệnh JS/krpano action. |
| `useScene` | Core | Quản lý scenes: chuyển scene, load scene mới, lấy thông tin scenes hiện có. |
| `useSound` | Core | Quản lý âm thanh: play, pause, stop, điều chỉnh volume, loop. |
| `useUtility` | Core | Các hàm tiện ích: lấy thông tin trạng thái, chuyển đổi tọa độ, tính toán góc nhìn. |
| `useView` | Core | Quản lý view (camera): thay đổi hlookat, vlookat, fov, zoom, và theo dõi thay đổi view. |
| `useLoading` | Core | Theo dõi trạng thái load panorama, hiển thị progress hoặc loading overlay. |
---
| Hook | Nhóm | Chức năng / Mô tả |
| ---------------------- | ------ | --------------------------------------------------------------------------------------------------------------------- |
| `useKrpanoEventBridge` | Events | Hook core để tạo **bridge giữa Krpano → React**, nhận mọi event từ Krpano. |
| `useKrpanoCommand` | Events | Truy cập trực tiếp Krpano API từ context, cho phép gọi lệnh và đọc trạng thái Krpano. |
| `useLayerEvents` | Events | Lắng nghe các event liên quan đến layer: click, hover (over), out. |
| `useHotspotEvent` | Events | Lắng nghe các event liên quan đến hotspot: click, hover, out, down, up. |
| `useViewEvents` | Events | Lắng nghe thay đổi view: viewchange, viewchanged, idle. |
| `useSceneEvents` | Events | Lắng nghe sự kiện scene: new scene, remove scene. |
| `useMouseEvents` | Events | Lắng nghe các sự kiện chuột: click, dblclick, mousedown, mouseup, mousemove. |
| `useSystemEvents` | Events | Lắng nghe các sự kiện hệ thống Krpano: onloadcomplete, onxmlcomplete, onready, onerror, onresize, onfullscreenchange. |
| `useKeyboardEvents` | Events | Lắng nghe các sự kiện bàn phím: keydown, keyup, kèm thông tin keycode và modifier keys. |
| `useKrpanoEventListener` | Events | Quản lý sự kiện từ Krpano → React. Cho phép đăng ký, xóa, trigger các event hệ thống, scene, view, hotspot, layer, mouse, keyboard. Hỗ trợ cleanup tự động, setup hotspot/layer event dễ dàng và trigger custom event từ React.keys. |
---
# Bắt Đầu
## Khởi tạo `KrpanoProvider`
`KrpanoProvider` là **context provider** chịu trách nhiệm quản lý trạng thái và các API chung cho Krpano trong toàn bộ ứng dụng. Bạn cần **bọc toàn bộ ứng dụng** trong `main.js` (hoặc file root của React) để mọi component con có thể truy cập Krpano context.
```jsx
// main.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { KrpanoProvider } from "./contexts/KrpanoProvider";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<KrpanoProvider>
<App />
</KrpanoProvider>
</React.StrictMode>
);
```
✅ **Lưu ý:** `KrpanoProvider` phải nằm **ngoài App**, để toàn bộ component trong App có thể sử dụng Krpano context.
---
`KrpanoViewer` là **component chính để hiển thị panorama**. Bạn chỉ cần đặt component này vào nơi muốn hiển thị panorama.
```jsx
// App.js
import React from "react";
import { KrpanoViewer } from "./components/KrpanoViewer";
function App() {
return (
<div style={{ width: "100%", height: "100vh" }}>
<KrpanoViewer
path="./krpano" // Đường dẫn tới thư mục chứa pano
xmlName="tour.xml" // (Tùy chọn) Tên file XML, mặc định là "tour.xml"
jsName="tour.js" // (Tùy chọn) Tên file JS kèm theo, mặc định là "tour.js"
swfName="./krpano/tour.swf" // (Tùy chọn) Tên file SWF, mặc định "./krpano/tour.swf"
style={{ width: "100%", height: "100%" }} // Tùy chỉnh CSS cho container
/>
</div>
);
}
export default App;
```
| Prop | Type | Mặc định | Mô tả |
| --------- | -------- | --------------------- | ---------------------------------------------------------------- |
| `path` | `string` | **bắt buộc** | Đường dẫn tới thư mục chứa file panorama (XML, JS, SWF, images). |
| `xmlName` | `string` | `"tour.xml"` | Tên file XML cấu hình panorama. |
| `jsName` | `string` | `"tour.js"` | Tên file JS kèm theo (nếu có). |
| `swfName` | `string` | `"./krpano/tour.swf"` | Tên file SWF cho fallback Flash (nếu cần). |
| `style` | `object` | `{}` | Tùy chỉnh CSS cho container Krpano. |
---
1. `path` phải **chính xác tới thư mục chứa các file panorama** (`XML, JS, SWF, tiles`...).
2. Nếu bạn không truyền `xmlName`, `jsName` hoặc `swfName`, `KrpanoViewer` sẽ sử dụng **giá trị mặc định**.
3. Container của `KrpanoViewer` cần **có kích thước rõ ràng** (`width` và `height`), nếu không panorama sẽ không hiển thị đúng.
4. `KrpanoProvider` phải bọc toàn bộ ứng dụng để **hỗ trợ các hook và API context** từ Krpano.
---
Mục đích: Khi React muốn điều khiển panorama (Krpano), ví dụ thay đổi view, bật layer, zoom…
Ví dụ: Click button trong React để show/hide layer:
### 1. loadScene() - Chuyển scene
**Mục đích**: Chuyển đổi giữa các scene trong tour 360°
```tsx
const krpano = useKrpano();
// ✅ Chuyển scene cơ bản
krpano.scene.loadScene('bedroom');
// ✅ Chuyển scene với hiệu ứng blend
krpano.scene.loadScene('kitchen', {
blend: 'BLEND(2.0)', // Fade 2 giây
flags: 'MERGE' // Merge với scene hiện tại
});
// ✅ Chuyển scene với hiệu ứng crossfade
krpano.scene.loadScene('livingroom', {
blend: 'CROSSBLEND(1.5)' // Crossfade 1.5 giây
});
// ✅ Chuyển scene ngay lập tức
krpano.scene.loadScene('bathroom', {
blend: 'NOBLEND'
});
```
**Mục đích**: Xóa scene khỏi bộ nhớ để tiết kiệm tài nguyên
```tsx
// ✅ Xóa scene không cần thiết
krpano.scene.removeScene('old_scene');
// ✅ Xóa nhiều scene trong loop
const unusedScenes = ['temp1', 'temp2', 'temp3'];
unusedScenes.forEach(scene => {
krpano.scene.removeScene(scene);
});
```
**Mục đích**: Thêm scene động vào tour
```tsx
// ✅ Thêm scene trống
krpano.scene.addScene('new_scene');
// ✅ Thêm scene và load XML
krpano.scene.addScene('outdoor', '/xml/outdoor.xml');
// ✅ Thêm scene cho gallery động
const addGalleryScene = (imageUrl: string, sceneName: string) => {
krpano.scene.addScene(sceneName);
krpano.utility.set(`scene[${sceneName}].image.sphere`, imageUrl);
};
```
**Mục đích**: Tạo bản sao của scene để tùy chỉnh
```tsx
// ✅ Sao chép scene làm template
krpano.scene.copyScene('master_bedroom', 'bedroom_variant');
// ✅ Tạo nhiều variant của cùng một scene
const createVariants = (baseScene: string) => {
krpano.scene.copyScene(baseScene, `${baseScene}_night`);
krpano.scene.copyScene(baseScene, `${baseScene}_day`);
};
```
---
**Mục đích**: Điều khiển hướng nhìn camera với animation mượt
```tsx
// ✅ Nhìn về hướng cụ thể với animation
krpano.view.lookAt(90, 0, 80, {
smooth: true,
time: 2.0,
tween: 'easeInOutSine'
});
// ✅ Nhìn ngay lập tức (không animation)
krpano.view.lookAt(180, -10, 90, { smooth: false });
// ✅ Tạo camera path (tour tự động)
const cameraPath = [
{ h: 0, v: 0, f: 90 },
{ h: 90, v: 10, f: 70 },
{ h: 180, v: -5, f: 100 },
{ h: 270, v: 5, f: 80 }
];
const startAutoTour = () => {
cameraPath.forEach((point, index) => {
setTimeout(() => {
krpano.view.lookAt(point.h, point.v, point.f, {
smooth: true,
time: 3.0,
tween: 'easeInOutQuad'
});
}, index * 4000);
});
};
// ✅ Nhìn về hotspot
const lookAtHotspot = (hotspotName: string) => {
const h = krpano.utility.get(`hotspot[${hotspotName}].ath`);
const v = krpano.utility.get(`hotspot[${hotspotName}].atv`);
krpano.view.lookAt(h, v, undefined, { smooth: true, time: 1.5 });
};
```
**Mục đích**: Di chuyển camera mượt đến tọa độ cụ thể
```tsx
// ✅ Di chuyển mượt đến vị trí
krpano.view.lookTo(45, 15, { smooth: true, time: 1.0 });
// ✅ Snap đến vị trí ngay lập tức
krpano.view.lookTo(90, 0, { smooth: false });
// ✅ Tạo shake effect
const shakeCamera = () => {
const original = {
h: krpano.utility.get('view.hlookat'),
v: krpano.utility.get('view.vlookat')
};
for (let i = 0; i < 10; i++) {
setTimeout(() => {
krpano.view.lookTo(
original.h + Math.random() * 4 - 2,
original.v + Math.random() * 4 - 2,
{ smooth: false }
);
}, i * 50);
}
setTimeout(() => {
krpano.view.lookTo(original.h, original.v, { smooth: true, time: 0.5 });
}, 500);
};
```
**Mục đích**: Thay đổi field of view (độ rộng góc nhìn)
```tsx
// ✅ Zoom in mượt
krpano.view.zoomTo(30, { smooth: true, time: 1.5 }); // Zoom in
// ✅ Zoom out mượt
krpano.view.zoomTo(120, { smooth: true, time: 1.0 }); // Zoom out
// ✅ Zoom ngay lập tức
krpano.view.zoomTo(90, { smooth: false }); // Standard view
// ✅ Tạo zoom pulse effect
const zoomPulse = () => {
const originalFov = krpano.utility.get('view.fov');
krpano.view.zoomTo(originalFov - 20, { smooth: true, time: 0.3 });
setTimeout(() => {
krpano.view.zoomTo(originalFov, { smooth: true, time: 0.3 });
}, 300);
};
// ✅ Zoom levels preset
const zoomLevels = {
wide: 120,
normal: 90,
medium: 60,
close: 30,
macro: 15
};
const setZoomLevel = (level: keyof typeof zoomLevels) => {
krpano.view.zoomTo(zoomLevels[level], { smooth: true, time: 1.0 });
};
```
**Mục đích**: Di chuyển camera theo các hướng cơ bản
```tsx
// ✅ Di chuyển cơ bản
krpano.view.moveCamera('up', 10); // Lên 10 độ
krpano.view.moveCamera('down', 5); // Xuống 5 độ
krpano.view.moveCamera('left', 15); // Trái 15 độ
krpano.view.moveCamera('right', 20); // Phải 20 độ
// ✅ Tạo camera drift effect
const startDrift = () => {
const drift = () => {
krpano.view.moveCamera('right', 0.5);
setTimeout(drift, 100);
};
drift();
};
// ✅ Keyboard controls simulation
const handleKeyboard = (key: string) => {
switch(key) {
case 'ArrowUp': krpano.view.moveCamera('up', 5); break;
case 'ArrowDown': krpano.view.moveCamera('down', 5); break;
case 'ArrowLeft': krpano.view.moveCamera('left', 5); break;
case 'ArrowRight': krpano.view.moveCamera('right', 5); break;
}
};
```
**Mục đích**: Dừng tất cả animation và chuyển động
```tsx
// ✅ Dừng tất cả chuyển động
krpano.view.stopMovement();
// ✅ Emergency stop cho auto tour
const emergencyStop = () => {
krpano.view.stopMovement();
console.log('Tour stopped by user');
};
```
---
**Mục đích**: Hiển thị hotspot/layer/plugin với hoặc không có animation
```tsx
// ✅ Hiển thị ngay lập tức
krpano.element.show('hotspot', 'info_button');
// ✅ Hiển thị với fade in
krpano.element.show('hotspot', 'menu', {
time: 1.0,
tween: 'easeOutSine'
});
// ✅ Hiển thị layer với slide effect
krpano.element.show('layer', 'sidebar', {
time: 0.8,
tween: 'easeOutBack'
});
// ✅ Hiển thị theo sequence
const showSequence = (elements: string[]) => {
elements.forEach((element, index) => {
setTimeout(() => {
krpano.element.show('hotspot', element, { time: 0.5 });
}, index * 200);
});
};
showSequence(['btn1', 'btn2', 'btn3', 'btn4']);
// ✅ Conditional show
const showIfCondition = (elementName: string, condition: boolean) => {
if (condition) {
krpano.element.show('hotspot', elementName);
}
};
```
**Mục đích**: Ẩn hotspot/layer/plugin với animation
```tsx
// ✅ Ẩn ngay lập tức
krpano.element.hide('hotspot', 'temp_marker');
// ✅ Ẩn với fade out
krpano.element.hide('layer', 'overlay', {
time: 0.8,
tween: 'easeInSine'
});
// ✅ Ẩn tất cả hotspots
const hideAllHotspots = (hotspotNames: string[]) => {
hotspotNames.forEach(name => {
krpano.element.hide('hotspot', name, { time: 0.3 });
});
};
// ✅ Auto-hide sau thời gian
const autoHide = (elementName: string, delay: number) => {
setTimeout(() => {
krpano.element.hide('layer', elementName, { time: 0.5 });
}, delay);
};
```
**Mục đích**: Bật/tắt hiển thị element
```tsx
// ✅ Toggle cơ bản
krpano.element.toggle('layer', 'menu');
// ✅ Toggle với tracking state
let menuVisible = false;
const toggleMenu = () => {
krpano.element.toggle('layer', 'navigation');
menuVisible = !menuVisible;
console.log('Menu is now:', menuVisible ? 'visible' : 'hidden');
};
// ✅ Toggle group
const toggleGroup = (groupName: string) => {
const elements = [`${groupName}_1`, `${groupName}_2`, `${groupName}_3`];
elements.forEach(element => {
krpano.element.toggle('hotspot', element);
});
};
```
**Mục đích**: Tạo các hiệu ứng animation phức tạp
```tsx
// ✅ Animation đơn giản
krpano.element.animate('layer', 'popup', {
x: 100,
y: 200,
alpha: 1.0
}, { time: 1.0, tween: 'easeOutBounce' });
// ✅ Animation phức tạp với nhiều thuộc tính
krpano.element.animate('hotspot', 'logo', {
scale: 1.2,
rotate: 360,
alpha: 0.8,
x: 50,
y: -30
}, {
time: 2.0,
tween: 'easeInOutElastic',
onComplete: 'trace(Animation completed!)'
});
// ✅ Pulse animation
const pulseAnimation = (elementName: string) => {
krpano.element.animate('hotspot', elementName, {
scale: 1.3,
alpha: 0.7
}, { time: 0.5, tween: 'easeOutSine' });
setTimeout(() => {
krpano.element.animate('hotspot', elementName, {
scale: 1.0,
alpha: 1.0
}, { time: 0.5, tween: 'easeInSine' });
}, 500);
};
// ✅ Floating animation
const floatingAnimation = (elementName: string) => {
const float = () => {
krpano.element.animate('hotspot', elementName, {
y: krpano.utility.get(`hotspot[${elementName}].y`) - 10
}, { time: 2.0, tween: 'easeInOutSine' });
setTimeout(() => {
krpano.element.animate('hotspot', elementName, {
y: krpano.utility.get(`hotspot[${elementName}].y`) + 10
}, { time: 2.0, tween: 'easeInOutSine' });
setTimeout(float, 4000);
}, 2000);
};
float();
};
// ✅ Shake animation
const shakeElement = (elementName: string) => {
for (let i = 0; i < 6; i++) {
setTimeout(() => {
krpano.element.animate('hotspot', elementName, {
x: krpano.utility.get(`hotspot[${elementName}].x`) + (i % 2 === 0 ? 5 : -5)
}, { time: 0.1 });
}, i * 100);
}
};
```
**Mục đích**: Tạo hotspot/layer/plugin động
```tsx
// ✅ Thêm hotspot cơ bản
krpano.element.addElement('hotspot', 'new_marker', {
url: 'images/marker.png',
ath: 90,
atv: 0,
scale: 1.0,
visible: true
});
// ✅ Thêm layer thông tin
krpano.element.addElement('layer', 'info_panel', {
url: 'images/panel.png',
x: 100,
y: 100,
width: 300,
height: 200,
alpha: 0.9,
visible: true
});
// ✅ Thêm hotspot với sự kiện
krpano.element.addElement('hotspot', 'interactive_btn', {
url: 'images/button.png',
ath: 45,
atv: 10,
onclick: 'loadscene(next_room)'
});
// ✅ Tạo hotspot grid
const createHotspotGrid = (rows: number, cols: number) => {
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
krpano.element.addElement('hotspot', `grid_${i}_${j}`, {
url: 'images/grid_point.png',
ath: (360 / cols) * j,
atv: ((180 / rows) * i) - 90,
scale: 0.5,
alpha: 0.7
});
}
}
};
```
**Mục đích**: Xóa hotspot/layer/plugin không cần thiết
```tsx
// ✅ Xóa element đơn lẻ
krpano.element.removeElement('hotspot', 'temp_marker');
// ✅ Xóa nhiều elements
const removeElements = (elementNames: string[]) => {
elementNames.forEach(name => {
krpano.element.removeElement('hotspot', name);
});
};
// ✅ Dọn dẹp elements với prefix
const cleanupElementsWithPrefix = (prefix: string) => {
// Note: Trong thực tế cần get danh sách elements trước
const elements = ['temp_1', 'temp_2', 'temp_3']; // Mock data
elements.filter(name => name.startsWith(prefix))
.forEach(name => {
krpano.element.removeElement('hotspot', name);
});
};
```
**Mục đích**: Tạo bản sao của element để tùy chỉnh
```tsx
// ✅ Sao chép hotspot
krpano.element.cloneElement('hotspot', 'original_marker', 'cloned_marker');
// ✅ Tạo nhiều bản sao
const createClones = (originalName: string, count: number) => {
for (let i = 1; i <= count; i++) {
krpano.element.cloneElement('hotspot', originalName, `${originalName}_copy_${i}`);
}
};
// ✅ Clone và modify
const cloneAndModify = (original: string, newName: string, modifications: any) => {
krpano.element.cloneElement('hotspot', original, newName);
Object.entries(modifications).forEach(([key, value]) => {
krpano.utility.set(`hotspot[${newName}].${key}`, value);
});
};
```
---
**Mục đích**: Phát các hiệu ứng âm thanh và nhạc nền
```tsx
// ✅ Phát âm thanh cơ bản
krpano.sound.playSound('click_sound', 'audio/click.mp3');
// ✅ Phát nhạc nền với loop
krpano.sound.playSound('background_music', 'audio/ambient.mp3', {
volume: 0.3,
loops: -1 // Loop vô hạn
});
// ✅ Phát sound effect
krpano.sound.playSound('door_open', 'audio/door.wav', {
volume: 0.8,
loops: 1
});
// ✅ Phát âm thanh theo scene
const playSceneAmbient = (sceneName: string) => {
const soundMap = {
kitchen: 'audio/kitchen_ambient.mp3',
garden: 'audio/birds.mp3',
bedroom: 'audio/night_ambient.mp3'
};
if (soundMap[sceneName]) {
krpano.sound.playSound('scene_ambient', soundMap[sceneName], {
volume: 0.4,
loops: -1
});
}
};
// ✅ Random sound effect
const playRandomSound = () => {
const sounds = ['sound1.mp3', 'sound2.mp3', 'sound3.mp3'];
const randomSound = sounds[Math.floor(Math.random() * sounds.length)];
krpano.sound.playSound('random_sfx', `audio/${randomSound}`, { volume: 0.6 });
};
```
**Mục đích**: Dừng âm thanh đang phát
```tsx
// ✅ Dừng âm thanh cụ thể
krpano.sound.stopSound('background_music');
// ✅ Dừng âm thanh khi chuyển scene
const stopSceneSounds = () => {
krpano.sound.stopSound('scene_ambient');
krpano.sound.stopSound('scene_effects');
};
// ✅ Fade out trước khi stop
const fadeOutAndStop = (soundName: string) => {
// Fade volume to 0 over 2 seconds, then stop
krpano.utility.call(`tween(sound[${soundName}].volume, 0, 2.0, default, stopsound(${soundName}))`);
};
```
**Mục đích**: Tạm dừng âm thanh (có thể resume sau)
```tsx
// ✅ Pause/Resume music player
let musicPaused = false;
const toggleMusic = () => {
if (musicPaused) {
krpano.sound.playSound('background_music'); // Resume
} else {
krpano.sound.pauseSound('background_music');
}
musicPaused = !musicPaused;
};
// ✅ Pause khi user inactive
const handleUserInactivity = (inactive: boolean) => {
if (inactive) {
krpano.sound.pauseSound('scene_ambient');
} else {
krpano.sound.playSound('scene_ambient');
}
};
```
**Mục đích**: Thay đổi âm lượng của âm thanh đang phát
```tsx
// ✅ Điều chỉnh âm lượng cơ bản
krpano.sound.setSoundVolume('background_music', 0.5);
// ✅ Fade volume effect
const fadeVolume = (soundName: string, from: number, to: number, duration: number) => {
krpano.utility.call(`tween(sound[${soundName}].volume, ${to}, ${duration})`);
};
// ✅ Volume slider
const handleVolumeSlider = (soundName: string, sliderValue: number) => {
const volume = sliderValue / 100; // Convert 0-100 to 0-1
krpano.sound.setSoundVolume(soundName, volume);
};
// ✅ Distance-based volume (3D audio effect)
const updateSoundByDistance = (soundName: string, distance: number) => {
const maxDistance = 100;
const volume = Math.max(0, 1 - (distance / maxDistance));
krpano.sound.setSoundVolume(soundName, volume);
};
```
**Mục đích**: Dừng toàn bộ âm thanh trong scene
```tsx
// ✅ Emergency stop all sounds
krpano.sound.stopAllSounds();
// ✅ Mute toggle
let soundsMuted = false;
const toggleMute = () => {
if (soundsMuted) {
// Unmute - restart sounds
krpano.sound.playSound('background_music', null, { volume: 0.3, loops: -1 });
} else {
krpano.sound.stopAllSounds();
}
soundsMuted = !soundsMuted;
};
// ✅ Scene cleanup
const cleanupSceneSounds = () => {
krpano.sound.stopAllSounds();
console.log('All scene sounds stopped');
};
```
---
**Mục đích**: Điều khiển chế độ toàn màn hình
```tsx
// ✅ Enter fullscreen
krpano.control.enterFullscreen();
// ✅ Exit fullscreen
krpano.control.exitFullscreen();
// ✅ Toggle fullscreen
krpano.control.toggleFullscreen();
// ✅ Fullscreen with callback
const enterFullscreenMode = () => {
krpano.control.enterFullscreen();
console.log('Entered fullscreen mode');
};
// ✅ Conditional fullscreen
const conditionalFullscreen = (condition: boolean) => {
if (condition) {
krpano.control.enterFullscreen();
} else {
krpano.control.exitFullscreen();
}
};
```
**Mục đích**: Bật/tắt khả năng điều khiển của user
```tsx
// ✅ Enable user control
krpano.control.enableControl();
// ✅ Disable user control (for auto tour)
krpano.control.disableControl();
// ✅ Temporary disable during loading
const temporaryDisableControl = (duration: number) => {
krpano.control.disableControl();
setTimeout(() => {
krpano.control.enableControl();
}, duration);
};
// ✅ Conditional control based on user role
const setControlBasedOnRole = (userRole: 'admin' | 'viewer' | 'guest') => {
if (userRole === 'admin') {
krpano.control.enableControl();
} else if (userRole === 'viewer') {
krpano.control.enableControl();
} else {
krpano.control.disableControl(); // Guests can only watch
}
};
```
**Mục đích**: Thay đổi hình dạng con trỏ chuột
```tsx
// ✅ Set different cursor types
krpano.control.setCursor('normal');
krpano.control.setCursor('drag');
krpano.control.setCursor('dragging');
krpano.control.setCursor('moving');
// ✅ Interactive cursor feedback
const setCursorOnHover = (isHovering: boolean) => {
if (isHovering) {
krpano.control.setCursor('drag');
} else {
krpano.control.setCursor('normal');
}
};
// ✅ Context-based cursor
const setContextCursor = (mode: 'view' | 'edit' | 'measure') => {
switch(mode) {
case 'view':
krpano.control.setCursor('normal');
break;
case 'edit':
krpano.control.setCursor('drag');
break;
case 'measure':
// Custom cursor for measuring tool
krpano.control.setCursor('normal');
break;
}
};
```
**Mục đích**: Hiển thị cursor loading khi xử lý
```tsx
// ✅ Show loading cursor
krpano.control.showWaitcursor();
// ✅ Hide loading cursor
krpano.control.hideWaitcursor();
// ✅ Show during async operations
const performAsyncOperation = async () => {
krpano.control.showWaitcursor();
try {
// Simulate async operation
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('Operation completed');
} finally {
krpano.control.hideWaitcursor();
}
};
// ✅ Auto-hide after timeout
const showWaitWithTimeout = (timeout: number = 5000) => {
krpano.control.showWaitcursor();
setTimeout(() => {
krpano.control.hideWaitcursor();
}, timeout);
};
```
---
**Mục đích**: Tải cấu hình XML mới
```tsx
// ✅ Load new XML configuration
krpano.utility.loadXML('/config/new_tour.xml');
// ✅ Load XML with keep display
krpano.utility.loadXML('/config/addon.xml', { keepdisplay: true });
// ✅ Load XML based on user selection
const loadTourConfiguration = (tourType: 'basic' | 'premium' | 'vip') => {
const xmlPaths = {
basic: '/config/basic_tour.xml',
premium: '/config/premium_tour.xml',
vip: '/config/vip_tour.xml'
};
krpano.utility.loadXML(xmlPaths[tourType]);
};
// ✅ Dynamic XML loading
const loadDynamicConfig = (userId: string) => {
krpano.utility.loadXML(`/config/user_${userId}.xml`);
};
```
**Mục đích**: Tải lại toàn bộ tour
```tsx
// ✅ Simple reload
krpano.utility.reload();
// ✅ Reload with confirmation
const reloadWithConfirmation = () => {
if (confirm('Bạn có muốn tải lại tour?')) {
krpano.utility.reload();
}
};
// ✅ Auto-reload on error
const autoReloadOnError = () => {
// Simulate error detection
setTimeout(() => {
console.log('Error detected, reloading...');
krpano.utility.reload();
}, 5000);
};
// ✅ Reload with save state
const reloadWithState = () => {
// Save current state
const currentState = {
scene: krpano.utility.get('xml.scene'),
hlookat: krpano.utility.get('view.hlookat'),
vlookat: krpano.utility.get('view.vlookat'),
fov: krpano.utility.get('view.fov')
};
localStorage.setItem('tourState', JSON.stringify(currentState));
krpano.utility.reload();
};
```
**Mục đích**: Tạo ảnh chụp màn hình tour
```tsx
// ✅ Basic screenshot
krpano.utility.screenshot();
// ✅ High resolution screenshot
krpano.utility.screenshot({ scale: 2.0, alpha: false });
// ✅ Screenshot with transparency
krpano.utility.screenshot({ scale: 1.0, alpha: true });
// ✅ Screenshot for thumbnail generation
const generateThumbnail = () => {
krpano.utility.screenshot({ scale: 0.5, alpha: false });
console.log('Thumbnail generated');
};
// ✅ Batch screenshot for all scenes
const screenshotAllScenes = (sceneNames: string[]) => {
sceneNames.forEach((sceneName, index) => {
setTimeout(() => {
krpano.scene.loadScene(sceneName);
setTimeout(() => {
krpano.utility.screenshot({ scale: 1.0 });
console.log(`Screenshot taken for ${sceneName}`);
}, 1000);
}, index * 2000);
});
};
```
**Mục đích**: Ghi log debug thông tin
```tsx
// ✅ Simple trace
krpano.utility.trace('Debug message');
// ✅ Trace with variables
const debugCurrentState = () => {
const currentScene = krpano.utility.get('xml.scene');
const currentFov = krpano.utility.get('view.fov');
krpano.utility.trace(`Current scene: ${currentScene}, FOV: ${currentFov}`);
};
// ✅ Conditional tracing
const conditionalTrace = (condition: boolean, message: string) => {
if (condition) {
krpano.utility.trace(`DEBUG: ${message}`);
}
};
// ✅ Performance tracing
const tracePerformance = (operationName: string, startTime: number) => {
const endTime = performance.now();
const duration = endTime - startTime;
krpano.utility.trace(`${operationName} took ${duration.toFixed(2)}ms`);
};
```
**Mục đích**: Ghi log lỗi
```tsx
// ✅ Simple error logging
krpano.utility.error('Something went wrong!');
// ✅ Error with context
const logErrorWithContext = (error: Error, context: string) => {
krpano.utility.error(`${context}: ${error.message}`);
};
// ✅ Try-catch with error logging
const safeOperation = () => {
try {
// Risky operation
throw new Error('Test error');
} catch (error) {
krpano.utility.error(`Operation failed: ${error.message}`);
}
};
```
**Mục đích**: Truy cập và thay đổi thuộc tính Krpano
```tsx
// ✅ Get values
const currentScene = krpano.utility.get('xml.scene');
const viewFov = krpano.utility.get('view.fov');
const hotspotVisible = krpano.utility.get('hotspot[info].visible');
// ✅ Set values
krpano.utility.set('view.fov', 90);
krpano.utility.set('hotspot[marker].alpha', 0.8);
krpano.utility.set('layer[menu].visible', true);
// ✅ Dynamic property access
const getHotspotProperty = (hotspotName: string, property: string) => {
return krpano.utility.get(`hotspot[${hotspotName}].${property}`);
};
const setHotspotProperty = (hotspotName: string, property: string, value: any) => {
krpano.utility.set(`hotspot[${hotspotName}].${property}`, value);
};
// ✅ Batch operations
const setMultipleProperties = (properties: Record<string, any>) => {
Object.entries(properties).forEach(([path, value]) => {
krpano.utility.set(path, value);
});
};
// Example usage:
setMultipleProperties({
'view.fov': 80,
'hotspot[info].alpha': 0.9,
'layer[menu].x': 100,
'layer[menu].y': 50
});
```
**Mục đích**: Thực thi lệnh Krpano trực tiếp
```tsx
// ✅ Simple call
krpano.utility.call('trace(Hello World)');
// ✅ Complex operations
krpano.utility.call('tween(view.fov, 60, 2.0, easeInOutSine)');
// ✅ Multiple commands
const executeMultipleCommands = () => {
krpano.utility.call(`
set(hotspot[marker].visible, true);
tween(hotspot[marker].alpha, 1.0, 1.0);
playsound(notification);
`);
};
// ✅ Conditional execution
const conditionalCall = (condition: boolean) => {
if (condition) {
krpano.utility.call('loadscene(next_room)');
} else {
krpano.utility.call('trace(Condition not met)');
}
};
```
**Mục đích**: Thực thi lệnh với nhiều tham số
```tsx
// ✅ Call with parameters
krpano.utility.callwith('loadscene', 'bedroom', 'null', 'BLEND(1.0)');
// ✅ Dynamic parameters
const callWithDynamicParams = (action: string, ...params: any[]) => {
krpano.utility.callwith(action, ...params);
};
// Example:
callWithDynamicParams('tween', 'view.fov', 90, 2.0, 'easeOutSine');
```
**Mục đích**: Trì hoãn thực thi lệnh
```tsx
// ✅ Simple wait
krpano.utility.wait(2.0); // Wait 2 seconds
// ✅ Wait with callback
krpano.utility.wait(1.5, 'trace(Timer finished)');
// ✅ Sequential operations with delays
const sequentialOperations = () => {
krpano.utility.call('trace(Step 1)');
krpano.utility.wait(1.0, 'trace(Step 2)');
krpano.utility.wait(2.0, 'loadscene(next_room)');
krpano.utility.wait(3.0, 'trace(Sequence completed)');
};
// ✅ Timed auto-tour
const startTimedAutoTour = (scenes: string[], interval: number) => {
scenes.forEach((scene, index) => {
krpano.utility.wait(interval * index, `loadscene(${scene})`);
});
};
```
---
**Mục đích**: Thực thi lệnh với type safety
```tsx
const krpano = useKrpano();
// ✅ Set hotspot properties
krpano.execute({
type: 'hotspot',
name: 'info_button',
options: {
visible: true,
alpha: 0.9,
scale: 1.2,
handcursor: true
}
});
// ✅ Set layer properties
krpano.execute({
type: 'layer',
name: 'overlay',
options: {
x: 100,
y: 200,
width: 300,
height: 150,
alpha: 0.8,
visible: true
}
});
// ✅ Set view properties
krpano.execute({
type: 'view',
options: {
hlookat: 90,
vlookat: 10,
fov: 80,
fovmin: 30,
fovmax: 150
}
});
// ✅ Raw command execution
krpano.execute({
raw: 'tween(view.fov, 60, 2.0, easeInOutSine, trace(Zoom completed))'
});
// ✅ Plugin configuration
krpano.execute({
type: 'plugin',
name: 'webvr',
options: {
enabled: true,
keeptexture: true,
showstereosupport: false
}
});
// ✅ Sound configuration
krpano.execute({
type: 'sound',
name: 'ambient',
options: {
url: 'audio/forest.mp3',
volume: 0.6,
loops: -1
}
});
// ✅ Events setup
krpano.execute({
type: 'events',
options: {
onloadcomplete: 'trace(Tour loaded successfully)',
onresize: 'trace(Window resized)',
onclick: 'trace(User clicked)'
}
});
// ✅ Display settings
krpano.execute({
type: 'display',
options: {
width: '100%',
height: '100%',
align: 'center',
background: '#000000'
}
});
```
---
```tsx
const krpano = useKrpano();
// ✅ Quick show/hide (default to hotspot)
krpano.show('info_marker');
krpano.hide('temp_marker');
krpano.toggle('menu_button');
// ✅ Specify element type
krpano.show('sidebar', 'layer');
krpano.hide('overlay', 'layer');
krpano.toggle('audio_player', 'plugin');
// ✅ Quick scene loading
krpano.loadScene('kitchen');
// ✅ Quick camera control
krpano.lookAt(180, 0, 90);
krpano.zoomTo(60);
// ✅ Combined shortcuts
const quickTour = () => {
krpano.show('loading', 'layer');
krpano.loadScene('bedroom');
krpano.lookAt(90, 10, 80);
krpano.hide('loading', 'layer');
};
```
---
```tsx
const createAutoTour = () => {
const scenes = [
{ name: 'entrance', lookAt: [0, 0, 90], duration: 3000 },
{ name: 'living_room', lookAt: [90, -5, 85], duration: 4000 },
{ name: 'kitchen', lookAt: [180, 0, 95], duration: 3500 },
{ name: 'bedroom', lookAt: [270, 10, 75], duration: 4000 }
];
let currentIndex = 0;
const nextScene = () => {
if (currentIndex < scenes.length) {
const scene = scenes[currentIndex];
// Show loading
krpano.element.show('layer', 'loading', { time: 0.3 });
// Load scene
krpano.scene.loadScene(scene.name, { blend: 'BLEND(1.5)' });
// Set camera position
krpano.view.lookAt(scene.lookAt[0], scene.lookAt[1], scene.lookAt[2], {
smooth: true,
time: 2.0,
tween: 'easeInOutSine'
});
// Hide loading
setTimeout(() => {
krpano.element.hide('layer', 'loading', { time: 0.3 });
}, 1500);
// Play scene sound
krpano.sound.playSound('scene_ambient', `audio/${scene.name}.mp3`, {
volume: 0.4,
loops: 1
});
currentIndex++;
// Schedule next scene
if (currentIndex < scenes.length) {
setTimeout(nextScene, scene.duration);
} else {
// Tour completed
krpano.element.show('layer', 'tour_complete', { time: 1.0 });
krpano.sound.playSound('completion', 'audio/complete.mp3');
}
}
};
nextScene();
};
```
```tsx
const createInteractiveHotspots = () => {
const hotspots = [
{
name: 'info_1',
position: [45, 10],
info: 'This is the living room area',
image: 'images/info.png'
},
{
name: 'info_2',
position: [135, -5],
info: 'Kitchen with modern appliances',
image: 'images/kitchen.png'
},
{
name: 'info_3',
position: [225, 15],
info: 'Comfortable bedroom space',
image: 'images/bedroom.png'
}
];
hotspots.forEach((hotspot, index) => {
// Create hotspot
krpano.element.addElement('hotspot', hotspot.name, {
url: 'images/hotspot_marker.png',
ath: hotspot.position[0],
atv: hotspot.position[1],
scale: 0.8,
alpha: 0.7,
onclick: `showInfo(${hotspot.name})`
});
// Create info layer
krpano.element.addElement('layer', `${hotspot.name}_info`, {
url: hotspot.image,
x: 100,
y: 100,
width: 300,
height: 200,
alpha: 0,
visible: false,
zorder: 1000
});
// Pulse animation
setTimeout(() => {
krpano.element.animate('hotspot', hotspot.name, {
scale: 1.0,
alpha: 1.0
}, {
time: 0.5,
tween: 'easeOutBounce'
});
}, index * 500);
});
// Global function for showing info
(window as any).showInfo = (hotspotName: string) => {
// Hide all info panels
hotspots.forEach(h => {
krpano.element.hide('layer', `${h.name}_info`, { time: 0.3 });
});
// Show selected info
krpano.element.show('layer', `${hotspotName}_info`, { time: 0.5 });
// Auto hide after 5 seconds
setTimeout(() => {
krpano.element.hide('layer', `${hotspotName}_info`, { time: 0.5 });
}, 5000);
};
};
```
```tsx
const createAdaptiveQuality = () => {
const checkPerformance = () => {
// Simulate performance check
const fps = 60; // Mock FPS
const devicePixelRatio = window.devicePixelRatio || 1;
const isMobile = /Mobi|Android/i.test(navigator.userAgent);
let quality: 'low' | 'medium' | 'high' = 'medium';
if (isMobile || fps < 30) {
quality = 'low';
} else if (fps > 50 && devicePixelRatio > 1) {
quality = 'high';
}
return quality;
};
const applyQualitySettings = (quality: 'low' | 'medium' | 'high') => {
const settings = {
low: {
details: 8,
tessellation: 0.5,
renderinterface: false
},
medium: {
details: 12,
tessellation: 1.0,
renderinterface: true
},
high: {
details: 16,
tessellation: 1.5,
renderinterface: true
}
};
const config = settings[quality];
krpano.execute({
type: 'display',
options: {
details: config.details,
tessellation: config.tessellation,
renderinterface: config.renderinterface
}
});
krpano.utility.trace(`Applied ${quality} quality settings`);
};
const currentQuality = checkPerformance();
applyQualitySettings(currentQuality);
};
```
```tsx
const createUserPreferenceSystem = () => {
const defaultPreferences = {
autoRotate: false,
soundEnabled: true,
soundVolume: 0.7,
showHelp: true,
quality: 'medium' as 'low' | 'medium' | 'high',
controlSensitivity: 1.0
};
const loadPreferences = () => {
const saved = localStorage.getItem('krpano_preferences');
return saved ? { ...defaultPreferences, ...JSON.parse(saved) } : defaultPreferences;
};
const savePreferences = (preferences: typeof defaultPreferences) => {
localStorage.setItem('krpano_preferences', JSON.stringify(preferences));
};
const applyPreferences = (preferences: typeof defaultPreferences) => {
// Apply sound settings
if (preferences.soundEnabled) {
krpano.sound.playSound('ambient', 'audio/ambient.mp3', {
volume: preferences.soundVolume,
loops: -1
});
} else {
krpano.sound.stopAllSounds();
}
// Apply control sensitivity
krpano.execute({
type: 'control',
options: {
mousetype: preferences.controlSensitivity > 1 ? 'drag' : 'moveto',
dragspeed: preferences.controlSensitivity
}
});
// Apply auto-rotate
if (preferences.autoRotate) {
startAutoRotate();
}
// Show/hide help
krpano.execute({
type: 'layer',
name: 'help',
options: {
visible: preferences.showHelp
}
});
};
const startAutoRotate = () => {
const rotate = () => {
const currentH = krpano.utility.get('view.hlookat');
krpano.view.lookAt((currentH + 1) % 360, 0, undefined, {
smooth: true,
time: 0.1
});
setTimeout(rotate, 100);
};
rotate();
};
const preferences = loadPreferences();
applyPreferences(preferences);
return {
updatePreference: (key: keyof typeof defaultPreferences, value: any) => {
const updated = { ...preferences, [key]: value };
savePreferences(updated);
applyPreferences(updated);
},
getPreferences: () => preferences
};
};
```
```tsx
const createAnalyticsSystem = () => {
const trackEvent = (eventName: string, data?: any) => {
// Send to analytics service
console.log('Analytics:', eventName, data);
// Could integrate with Google Analytics, etc.
if ((window as any).gtag) {
(window as any).gtag('event', eventName, data);
}
};
const trackSceneVisit = (sceneName: string) => {
trackEvent('scene_visit', {
scene_name: sceneName,
timestamp: new Date().toISOString(),
duration: 0 // Will be updated when leaving scene
});
};
const trackHotspotClick = (hotspotName: string) => {
trackEvent('hotspot_click', {
hotspot_name: hotspotName,
scene: krpano.utility.get('xml.scene'),
timestamp: new Date().toISOString()
});
};
const trackViewChange = () => {
const viewData = {
hlookat: krpano.utility.get('view.hlookat'),
vlookat: krpano.utility.get('view.vlookat'),
fov: krpano.utility.get('view.fov')
};
trackEvent('view_change', viewData);
};
// Setup automatic tracking
const setupAutoTracking = () => {
// Track scene changes
krpano.execute({
type: 'events',
options: {
onnewscene: 'trackSceneVisit(get(xml.scene))',
onviewchange: 'trackViewChange()'
}
});
};
setupAutoTracking();
return {
trackEvent,
trackSceneVisit,
trackHotspotClick,
trackViewChange
};
};
```
---
1. **Scene Management**: Chuyển cảnh, quản lý tour đa phòng
2. **Camera Control**: Điều khiển góc nhìn, tạo auto-tour
3. **Interactive Elements**: Hotspot tương tác, menu, popup
4. **Audio Experience**: Nhạc nền, sound effects theo context
5. **User Experience**: Fullscreen, loading states, preferences
6. **Performance**: Adaptive quality, optimization
7. **Analytics**: Tracking user behavior
8. **Accessibility**: Control options, user preferences
---
Hook này giúp **lắng nghe và quản lý các sự kiện từ Krpano**, bao gồm:
1. **Sự kiện hệ thống** (`onloadcomplete`, `onxmlcomplete`, `onready`, `onerror`, `onresize`, `onfullscreenchange`)
2. **Sự kiện scene** (`onnewscene`, `onremovescene`)
3. **Sự kiện view** (`onviewchange`, `onviewchanged`, `onidle`)
4. **Sự kiện mouse/keyboard** (`onclick`, `ondblclick`, `onmousedown`, `onmouseup`, `onmousemove`, `onkeydown`, `onkeyup`)
5. **Sự kiện hotspot** (`onhotspotclick`, `onhotspotover`, `onhotspotout`)
6. **Sự kiện layer** (`onlayerclick`, `onlayerover`, `onlayerout`)
Hook cung cấp các API để **thêm, xóa, trigger và quản lý sự kiện** một cách linh hoạt.
---
```tsx
import React, { useEffect } from "react";
import { useKrpanoEventListener } from "react-krpano-toolkit";
function MyComponent() {
const {
addEventListener,
removeEventListener,
setupHotspotEvents,
setupLayerEvents,
triggerCustomEvent
} = useKrpanoEventListener();
useEffect(() => {
// Lắng nghe sự kiện toàn cục từ Krpano
const cleanup = addEventListener("onready", (data) => {
console.log("Krpano ready!", data);
});
// Trả về cleanup function khi unmount
return () => cleanup();
}, [addEventListener]);
useEffect(() => {
// Setup hotspot events
const cleanupHotspot = setupHotspotEvents("hotspot1", {
onclick: (data) => console.log("Hotspot clicked:", data),
onover: (data) => console.log("Hotspot hover:", data),
onout: (data) => console.log("Hotspot out:", data),
});
// Setup layer events
const cleanupLayer = setupLayerEvents("layer1", {
onclick: (data) => console.log("Layer clicked:", data),
});
return () => {
cleanupHotspot();
cleanupLayer();
};
}, [setupHotspotEvents, setupLayerEvents]);
const triggerEvent = () => {
triggerCustomEvent("oncustom", { msg: "Hello from React!" });
};
return <button onClick={triggerEvent}>Trigger Custom Event</button>;
}
```
---
| Hàm | Mô tả | Tham số | Trả về |
| ------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------- |
| `addEventListener` | Thêm listener cho một sự kiện Krpano | `eventType: KrpanoEventType`, `handler: KrpanoEventHandler` | Hàm cleanup để xóa listener |
| `removeEventListener` | Xóa listener đã đăng ký | `eventType`, `handler` | - |
| `removeAllEventListeners` | Xóa tất cả listener hoặc theo eventType | `eventType?` | - |
| `registerEventHandlers` | Đăng ký nhiều handler cùng lúc | `handlers: KrpanoEventHandlers` | Hàm cleanup |
| `triggerCustomEvent` | Gửi sự kiện tùy chỉnh tới Krpano | `eventType: string`, `data: any` | - |
| `setupHotspotEvents` | Thiết lập listener cho hotspot | `hotspotName: string`, `{ onclick?, onover?, onout?, ondown?, onup? }` | Hàm cleanup |
| `setupLayerEvents` | Thiết lập listener cho layer | `layerName: string`, `{ onclick?, onover?, onout? }` | Hàm cleanup |
| `isListening` | Kiểm tra đang lắng nghe sự kiện | `eventType` | `boolean` |
| `getListenerCount` | Lấy số lượng listener đang đăng ký | `eventType` | `number` |
| `getAllListeners` | Lấy danh sách tất cả các eventType đang lắng nghe | - | `string[]` |
---
1. **Luôn gọi cleanup function** khi component unmount để tránh memory leak.
2. **Hotspot/Layer events** chỉ kích hoạt khi tên hotspot/layer đúng với tên bạn setup.
3. **triggerCustomEvent** có thể dùng để gửi event từ React xuống Krpano, ví dụ kết hợp với hotspot/layer custom.
4. Hook **yêu cầu `KrpanoProvider`** phải bọc component, nếu không sẽ throw lỗi.
---
<!-- CONTACT -->
Vũ Văn Định - [@your_github](https://github.com/vuvandinh203) - vuvandinh.work@gmail.com
Project Link: [https://github.com/vuvandinh203/react-krpano-toolkit](https://github.com/vuvandinh203/react-krpano-toolkit)
<p align="right">(<a href="#readme-top">back to top</a>)</p>
Vũ Văn Định - [@your_github](https: