Have you ever wondered how elevator systems operate behind the scenes? I recently developed a real-time elevator control simulation using Vue 3, TypeScript, and Tailwind CSS a project that brings the complexity of multi-elevator coordination to life through a modern web interface.
In this post, I’ll walk you through the architecture, implementation details, and key lessons learned while building this interactive simulation from the ground up.
Project Overview
This simulation models a 10-floor building served by 4 independently operating elevators. Each elevator intelligently picks up passengers, navigates between floors, and drops them off at their destination all in real time with state updates every 10 seconds.
Live Demo Highlights
Four elevators running concurrently
Real-time passenger pickup & drop-off
Dynamic floor request generation
Visual floor indicators with request tracking
Scrollable, real-time activity logs
Modern, responsive user interface
Tech Stack
Frontend Framework: Vue 3 + Composition API
Language: TypeScript
Styling: Tailwind CSS
Build Tool: Vite
State Management: Vue’s built-in reactivity & composables
System Architecture
Core Data Structures
To ensure clarity and type safety, the system relies on three main TypeScript interfaces:
src/
├── components/
│ ├── ElevatorCard.vue
│ └── elevator/
│ ├── ElevatorStatus.vue
│ ├── FloorDisplay.vue
│ ├── PassengerInfo.vue
│ └── ElevatorLogs.vue
├── composables/
│ └── useElevatorSystem.ts
├── utils/
│ └── elevatorUtils.ts
└── types/
└── elevator.ts
Key Implementation Details
- Elevator Movement Logic
A basic but effective algorithm handles direction reversal and floor progression:
function moveElevator(elevator: Elevator) {
if (elevator.direction === 'up') {
if (elevator.currentFloor < elevator.flors.length) {
elevator.currentFloor++
log(elevator, `Moved up to floor ${elevator.currentFloor}`)
} else {
elevator.direction = 'down'
elevator.currentFloor--
log(elevator, `Reached top. Reversing to down → floor ${elevator.currentFloor}`)
}
} else {
if (elevator.currentFloor > 1) {
elevator.currentFloor--
log(elevator, `Moved down to floor ${elevator.currentFloor}`)
} else {
elevator.direction = 'up'
elevator.currentFloor++
log(elevator, `Reached bottom. Reversing to up → floor ${elevator.currentFloor}`)
}
}
}
- Passenger Pickup & Drop-Off
Passengers are generated and assigned direction-based destinations:
function handlePickup(elevator: Elevator, floor: Floor) {
const waitingPassengers = floor.up || floor.down ? 1 : 0
if (waitingPassengers > 0) {
const newPassenger: Passenger = {
id: Date.now(),
destinationFloor: Math.floor(Math.random() * 10) + 1,
direction: floor.up ? 'up' : 'down'
}
if (floor.up) {
while (newPassenger.destinationFloor <= elevator.currentFloor) {
newPassenger.destinationFloor = Math.floor(Math.random() * 10) + 1
}
}
elevator.passengers.push(newPassenger)
log(elevator, `Picked up passenger going to floor ${newPassenger.destinationFloor}`)
floor.up = false
floor.down = false
}
}
- System Controller
The main composable manages the elevator system in real time:
function useElevatorSystem(totalFloors: number = 10, maxPassengers: number = 5) {
const elevators = ref<Elevator[]>([])
let moveIntervalId: ReturnType<typeof setInterval> | null = null
function startSystem() {
moveIntervalId = setInterval(() => {
elevators.value.forEach(elevator => {
dropOffPassengers(elevator)
moveElevator(elevator)
const currentFloorObj = elevator.flors.find(f => f.id === elevator.currentFloor)
if (currentFloorObj && (currentFloorObj.up || currentFloorObj.down)) {
handlePickup(elevator, currentFloorObj)
}
dropOffPassengers(elevator)
})
elevators.value.forEach(generateRandomFloorRequest)
}, 10000)
}
return { elevators, initializeElevators, startSystem, stopSystem }
}
User Interface Design
Floor Display
Floor number with a circular badge
Highlighted active floor
Up/down request indicators
Clear floor labeling
Real-Time Feedback
Live updates for each elevator
Passenger count & destinations
Scrollable log with timestamps
Animated visual cues for movement
Challenges & Solutions
State Management Complexity
Managing 4 elevators with independent logic and shared global state was no small feat.
Solution: Leveraged Vue 3’s Composition API and ref/reactive to create performant, scoped reactivity.
Real-Time Synchronization
Ensuring consistent updates while keeping elevator logic independent.
Solution: I used a centralized interval loop to orchestrate operations while preserving elevator autonomy.
Type Safety
Elevators, floors, and passengers required intricate interrelations.
Solution: Strongly typed interfaces allowed safe interaction across components and composables.
Performance Highlights
Reactive efficiency via scoped updates
Efficient log storage with unshift() for quick inserts
Resource cleanup with interval teardown on unmount
Optimized rendering through component isolation
What’s Next?
The system offers exciting possibilities for further development:
Passenger Priorities: VIP or emergency overrides
Energy Efficiency: Smarter routing logic
Multi-Building Support: Shared elevator infrastructure
Analytics: Real-time performance dashboards
Interactive Mode: Manual elevator control by users
Lessons Learned
Composables = Clean architecture
TypeScript = Fewer bugs
Timing systems demand precision
Visual feedback = Better UX
Logging simplifies debugging & UX clarity
Conclusion
This project was a rewarding challenge that merged real-time systems thinking with modern frontend development. Vue 3’s reactivity, TypeScript’s type assurance, and Tailwind’s rapid styling capabilities came together to create a clean, scalable, and visually engaging simulation.
If you're passionate about algorithms, state management, or interactive web interfaces, I highly encourage exploring or extending this project.
Source Code
The full code is available on GitHub. Feel free to fork it, experiment with new features, or optimize the algorithms!
Project Links:
Live Demo: View Project
GitHub Repository: Smart-Elevator-Control-System