Balloon Viewer Component
An animated balloon component that grows when receiving click events and pops when reaching maximum size.
Overview
Component Name: balloon-viewerLatest Version: 1.0.0Author: BoardAPI Team License: MIT
The Balloon Viewer displays an animated balloon that responds to action:click events by growing larger. When it reaches the maximum size, it pops with an animation and resets.
Use Cases
- Visual feedback for interactive games
- Progress visualization (clicks = progress)
- Event-driven animations
- Tutorial demonstrations
- Gamification elements
Component Preview
┌─────────────────────┐
│ │
│ ☁️ │
│ (balloon) │
│ │
│ Size: 5/10 │
│ │
└─────────────────────┘States:
- Small (0 clicks): Tiny balloon
- Growing (1-9 clicks): Balloon gets progressively larger
- Max Size (10 clicks): Full-size balloon
- Pop! (auto): Balloon pops and resets to small
Props Schema
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
balloonColor | string | No | #f56565 | Balloon color in hex format |
maxSize | number | No | 10 | Maximum size before balloon pops |
Props Examples
Red balloon (default):
{
"balloonColor": "#f56565",
"maxSize": 10
}Blue balloon:
{
"balloonColor": "#4299e1",
"maxSize": 15
}Fast-popping yellow balloon:
{
"balloonColor": "#ffd93d",
"maxSize": 5
}Events
Listened Events
action:click
The balloon grows by one step when it receives this event.
Expected Event Structure:
{
type: 'board-event',
event: 'action:click',
data: {
clickCount: 5, // Current click count (optional)
timestamp: 1700000000
}
}Behavior:
- Each
action:clickincrements balloon size by 1 - Size is capped at
maxSizeprop value - When size reaches
maxSize, balloon pops automatically
Emitted Events
action:pop
Fired when the balloon reaches maximum size and pops.
Event Structure:
{
type: 'component-event',
event: 'action:pop',
data: {
finalSize: 10, // Size when popped (= maxSize)
totalClicks: 10, // Total clicks received
timestamp: 1700000000 // Unix timestamp
}
}Example Usage:
// In another component - listen for pop event
window.addEventListener('message', (event) => {
if (event.data.type === 'board-event' &&
event.data.event === 'action:pop') {
console.log('Balloon popped!', event.data.data);
// Trigger celebration animation, play sound, etc.
celebratePop();
}
});Dimensions
- Width: 280px
- Height: 360px
- Resizable: No
manifest.json
{
"name": "balloon-viewer",
"version": "1.0.0",
"description": "Animated balloon that grows on click events and pops",
"author": "BoardAPI Team",
"license": "MIT",
"entry": "index.html",
"component": {
"width": 280,
"height": 360,
"resizable": false,
"schema": {
"type": "object",
"properties": {
"balloonColor": {
"type": "string",
"default": "#f56565",
"description": "Balloon color (hex format)"
},
"maxSize": {
"type": "number",
"default": 10,
"description": "Maximum size before pop"
}
}
}
}
}Implementation Example
index.html (Simplified)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
margin: 0;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.balloon-container {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.balloon {
width: 60px;
height: 80px;
border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
position: relative;
transition: transform 0.3s ease;
animation: float 2s ease-in-out infinite;
}
.balloon::before {
content: '';
position: absolute;
width: 15px;
height: 20px;
background: inherit;
border-radius: 50%;
bottom: -8px;
left: 50%;
transform: translateX(-50%);
}
.balloon-string {
width: 2px;
height: 50px;
background: rgba(255, 255, 255, 0.6);
margin-top: 2px;
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-15px); }
}
.balloon.popping {
animation: pop 0.4s ease-out forwards;
}
@keyframes pop {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.3); opacity: 0.7; }
100% { transform: scale(0); opacity: 0; }
}
.size-indicator {
margin-top: 20px;
font-size: 14px;
}
</style>
</head>
<body>
<div class="balloon-container">
<div class="balloon" id="balloon"></div>
<div class="balloon-string"></div>
</div>
<div class="size-indicator" id="sizeIndicator">Size: 0/10</div>
<script>
// Get props from parent
let props = window.componentProps || {};
const maxSize = props.maxSize || 10;
const balloonColor = props.balloonColor || '#f56565';
// State
let currentSize = 0;
// Set balloon color
const balloon = document.getElementById('balloon');
balloon.style.background = balloonColor;
// Update size indicator
function updateSizeIndicator() {
document.getElementById('sizeIndicator').textContent =
`Size: ${currentSize}/${maxSize}`;
}
// Grow balloon
function growBalloon() {
if (currentSize >= maxSize) return;
currentSize++;
updateSizeIndicator();
// Scale balloon based on size (60px base + 15px per step)
const scale = 1 + (currentSize / maxSize) * 1.5;
balloon.style.transform = `scale(${scale})`;
// Check if max size reached
if (currentSize >= maxSize) {
setTimeout(popBalloon, 300);
}
}
// Pop balloon
function popBalloon() {
balloon.classList.add('popping');
// Emit pop event
window.parent.postMessage({
type: 'component-event',
event: 'action:pop',
data: {
finalSize: currentSize,
totalClicks: currentSize,
timestamp: Date.now()
}
}, '*');
// Reset after animation
setTimeout(() => {
balloon.classList.remove('popping');
currentSize = 0;
balloon.style.transform = 'scale(1)';
updateSizeIndicator();
}, 400);
}
// Listen for click events from other components
window.addEventListener('message', (event) => {
if (event.data.type === 'board-event' &&
event.data.event === 'action:click') {
growBalloon();
}
});
// Initialize
updateSizeIndicator();
</script>
</body>
</html>Usage on a Board
Adding via API
curl -X POST http://localhost:4000/api/v1/boards/{boardId}/objects \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"objectId": "balloon-1",
"type": "external-component",
"position": { "x": 300, "y": 100 },
"width": 280,
"height": 360,
"data": {
"componentUrl": "http://localhost:4000/components/balloon-viewer@1.0.0/index.html",
"props": {
"balloonColor": "#f56565",
"maxSize": 10
}
}
}'Combining with Other Components
The balloon-viewer is designed to work with event-emitting components like clicker-button.
Example Setup:
{
"objects": [
{
"objectId": "clicker-btn-1",
"type": "external-component",
"position": { "x": 70, "y": 100 },
"data": {
"componentUrl": ".../clicker-button@1.0.0/index.html"
}
},
{
"objectId": "balloon-1",
"type": "external-component",
"position": { "x": 290, "y": 100 },
"data": {
"componentUrl": ".../balloon-viewer@1.0.0/index.html",
"props": {
"balloonColor": "#4299e1",
"maxSize": 10
}
}
}
]
}Customization Ideas
Multiple Balloon Colors
Create a rainbow of balloons with different colors:
- Red:
#f56565 - Blue:
#4299e1 - Yellow:
#ffd93d - Green:
#48bb78 - Purple:
#9f7aea
Custom Pop Animation
Replace the scale animation with a burst effect:
@keyframes pop {
0% {
transform: scale(1);
opacity: 1;
filter: blur(0px);
}
50% {
transform: scale(1.5);
opacity: 0.5;
filter: blur(2px);
}
100% {
transform: scale(2);
opacity: 0;
filter: blur(5px);
}
}Sound Effects
Add pop sound using Web Audio API:
function playPopSound() {
const audioContext = new AudioContext();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
// Short burst sound
oscillator.frequency.value = 600;
oscillator.type = 'sine';
gainNode.gain.setValueAtTime(0.5, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.15);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.15);
}Progress Bar Addition
Show visual progress bar below balloon:
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<style>
.progress-bar {
width: 100px;
height: 8px;
background: rgba(255, 255, 255, 0.3);
border-radius: 4px;
margin-top: 10px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: white;
transition: width 0.3s ease;
width: 0%;
}
</style>
<script>
function updateProgressBar() {
const progressPercent = (currentSize / maxSize) * 100;
document.getElementById('progressFill').style.width = `${progressPercent}%`;
}
</script>Testing
Unit Test (Jest)
describe('Balloon Viewer Component', () => {
test('grows balloon on action:click event', () => {
const initialSize = currentSize;
// Simulate click event from board
window.postMessage({
type: 'board-event',
event: 'action:click',
data: { clickCount: 1 }
}, '*');
expect(currentSize).toBe(initialSize + 1);
});
test('emits action:pop when maxSize reached', (done) => {
const postMessageSpy = jest.spyOn(window.parent, 'postMessage');
// Trigger 10 clicks
for (let i = 0; i < 10; i++) {
window.postMessage({
type: 'board-event',
event: 'action:click'
}, '*');
}
setTimeout(() => {
expect(postMessageSpy).toHaveBeenCalledWith(
expect.objectContaining({
type: 'component-event',
event: 'action:pop',
data: expect.objectContaining({
finalSize: 10
})
}),
'*'
);
done();
}, 400);
});
});Related Components
- Clicker Button - Button that emits clicks
- Score Display - Score counter
- Progress Bar - Linear progress indicator
Related Documentation
Download
- Source Code: balloon-viewer-1.0.0.zip
- Live Demo: Try it on BoardAPI
Next: Score Display Component