Building a Cross‑Platform 3D Desktop Pet with Electron, Vue, and Dexie.js
This article introduces the open‑source “3D Desktop Pet” project, detailing how to create a cross‑platform Electron application that combines a transparent always‑on‑top window, Three.js‑driven 3D pet interactions, and Dexie.js‑based task management, along with performance optimizations and multi‑window reminder system.
When Anime Meets Productivity Tools
Recently I developed a cross‑platform desktop app called “小桌宠” (Little Desktop Pet) that merges a 3D pet with a task‑management system. The rabbit not only looks cute but also pops up reminders when you slack off, acting as a cyber‑supervisor.
Project is Open‑Source – Star It!
Repository: https://github.com/zlmica/3d-desktop-pet
Pre‑built installers for multiple platforms are available in the video description.
1. Transparent Window & Cross‑Process Communication (Core Tech)
Effect: The pet stays on top, and a right‑click menu can launch task/reminder windows.
function createWindow() {
const primaryDisplay = screen.getPrimaryDisplay()
const { width: screenWidth, height: screenHeight } = primaryDisplay.workAreaSize
win = new BrowserWindow({
icon: path.join(process.env.VITE_PUBLIC, 'rabbit.png'),
webPreferences: { preload: path.join(__dirname, 'preload.mjs') },
width: 180,
height: 200,
x: screenWidth - 200,
y: screenHeight - 150,
autoHideMenuBar: true,
transparent: true,
frame: false,
alwaysOnTop: true,
resizable: false,
hasShadow: false,
})
win.webContents.on('did-finish-load', () => {
win?.webContents.send('main-process-message', new Date().toLocaleString())
})
if (VITE_DEV_SERVER_URL) {
win.loadURL(VITE_DEV_SERVER_URL)
} else {
win.loadFile(path.join(RENDERER_DIST, 'index.html'))
}
win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true })
}Key code points:
transparent: true + frame: false → borderless transparent window
alwaysOnTop: true → keep window on top
IPC communication controls child‑window state
Pitfall tip: Click‑through issues are solved by calculating mouse position and dynamically adjusting the window size.
2. 3D Pet Interaction System (TresJS Magic)
Model loading & animation control:
const isPlaying = ref(false)
const play = async () => {
currentAction2.value.play()
isPlaying.value = true
}
const sleep = () => {
currentAction2.value.stop()
isPlaying.value = false
}
const hello = () => {
if (isPlaying.value) {
currentAction2.value.stop()
}
currentAction.value.reset()
currentAction.value.play()
currentAction.value.setLoop(THREE.LoopOnce, 1)
currentAction.value.clampWhenFinished = true
currentAction.value.getMixer().addEventListener('finished', () => {
currentAction.value.stop()
if (isPlaying.value) {
currentAction2.value.play()
}
})
}GLTFLoader loads skeletal‑animation models
AnimationMixer switches between greeting, moving, and idle states
Click‑through handling converts 3D coordinates to screen coordinates
Performance tweaks: idle state drops frame rate (30 FPS → 15 FPS) and rendering pauses when the window is off‑screen.
3. Task Management System (Dexie.js in Action)
Database design:
class PetAppDB extends Dexie {
tasks!: Table
reminders!: Table
constructor() {
super('petApp')
this.version(1).stores({
tasks: '++id, title, status, priority, createdAt, updatedAt, dueDate',
reminders: '++id, title, reminderTime, repeatType, isEnabled, lastAcknowledgedAt, createdAt',
})
}
async addTask(task) {
const timestamp = Date.now()
return await this.tasks.add({ ...task, createdAt: timestamp, updatedAt: timestamp })
}
async updateTask(id, changes) {
return await this.tasks.update(id, { ...changes, updatedAt: Date.now() })
}
// …other methods
}Smart sorting: unfinished > with due date > creation time
Auto‑expire detection via dueDate + timer
Offline storage: IndexedDB with local backup
Typical query example – tasks due in the next 24 hours:
// Get tasks whose dueDate is within the next 24 h
const upcomingTasks = await db.tasks
.where('dueDate')
.between(Date.now(), Date.now() + 86400000)
.toArray()4. Reminder System (Multi‑Window Coordination)
Architecture design:
export function useReminder() {
async function checkReminders() {
const reminders = await db.checkReminders()
reminderQueue.value = reminders
if (reminderQueue.value.length > 0) {
window.ipcRenderer.send('show-reminder-window')
}
}
}Main window polls for pending reminders
Reminder window manages a queue and shows animated cards
IPC messages: show-reminder-window / hide-reminder-window
Animation implementation: Vue TransitionGroup + CSS keyframes create slide‑in card effects.
5. Performance Optimization Practices
Window management: single‑instance child windows to avoid memory leaks
3D rendering: off‑screen canvas + on‑demand rendering
Database: batch operations + index tuning
Packaging: electron‑builder multi‑platform configuration
{
$schema: "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json",
appId: "YourAppID",
asar: true,
productName: "小桌宠",
directories: { output: "release/${version}" },
files: ["dist", "dist-electron"],
mac: { target: ["dmg"], artifactName: "${productName}-Mac-${version}-Installer.${ext}" },
win: { target: [{ target: "nsis", arch: ["x64"] }], artifactName: "${productName}-Windows-${version}-Setup.${ext}" },
nsis: { oneClick: false, perMachine: false, allowToChangeInstallationDirectory: true, deleteAppDataOnUninstall: false },
linux: { target: ["AppImage"], artifactName: "${productName}-Linux-${version}.${ext}" }
}Conclusion – Reflections on Desktop Development
The project demonstrates that Electron is a viable choice for modern desktop applications, offering rapid development and strong cross‑platform capabilities despite larger bundle sizes. Future plans include adding a pet‑swap system and more interactive features.
Read the follow‑up article about the pet‑swap feature: https://juejin.cn/post/7475544117415624739
Side note: integrating AI into the project saved a lot of development time – it’s simply awesome!
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.