π¨ Animations
OnigiriJS includes smooth, built-in animation helpers for common transitions using requestAnimationFrame for optimal performance.
Fade Animations
Fade In
// Basic fade in (default 300ms)
O('.element').fadeIn();
// With custom duration
O('.message').fadeIn(500);
// With callback
O('.notification').fadeIn(300, function() {
console.log('π Fade in complete!');
// 'this' is the DOM element
this.focus();
});
// Chain with other methods
O('.card')
.fadeIn(400)
.addClass('visible');
Fade Out
// Basic fade out
O('.element').fadeOut();
// With duration and callback
O('.alert').fadeOut(500, function() {
console.log('Alert hidden');
O(this).remove(); // Remove after fade
});
// Fade out multiple elements
O('.temp-message').fadeOut(300);
Fade Toggle
// Toggle visibility with fade
function toggleMessage() {
const msg = O('.message');
if (msg.css('display') === 'none') {
msg.fadeIn(300);
} else {
msg.fadeOut(300);
}
}
Slide Animations
Slide Down
// Reveal element by sliding down
O('.menu').slideDown();
// With duration
O('.dropdown').slideDown(400);
// With callback
O('.details').slideDown(300, function() {
console.log('π Details revealed!');
this.querySelector('input').focus();
});
Slide Up
// Hide element by sliding up
O('.menu').slideUp();
// With duration and callback
O('.panel').slideUp(500, function() {
console.log('Panel collapsed');
});
Slide Toggle
// Toggle with slide animation
O('.accordion-header').on('click', function() {
const content = O(this).siblings('.accordion-content');
if (content.css('display') === 'none') {
content.slideDown(300);
O(this).addClass('open');
} else {
content.slideUp(300);
O(this).removeClass('open');
}
});
Practical Examples
Notification System
function showNotification(message, type = 'info', duration = 3000) {
const notification = O(\`
<div class="notification notification-\${type}">
π \${message}
<button class="close">Γ</button>
</div>
\`);
// Add to container
O('#notifications').append(notification.elements[0]);
// Fade in
O(notification.elements[0]).fadeIn(300);
// Auto-hide after duration
setTimeout(() => {
O(notification.elements[0]).fadeOut(300, function() {
O(this).remove();
});
}, duration);
// Close button
O(notification.elements[0]).find('.close').on('click', function() {
O(this).parent().fadeOut(300, function() {
O(this).remove();
});
});
}
// Usage
showNotification('Order placed successfully!', 'success');
showNotification('Error processing payment', 'error');
showNotification('Loading data...', 'info');
Dropdown Menu
const dropdown = {
init() {
O('.dropdown-toggle').on('click', function(e) {
e.stopPropagation();
const menu = O(this).siblings('.dropdown-menu');
const isOpen = menu.css('display') !== 'none';
// Close all dropdowns
O('.dropdown-menu').slideUp(200);
O('.dropdown-toggle').removeClass('active');
// Open this dropdown
if (!isOpen) {
menu.slideDown(200);
O(this).addClass('active');
}
});
// Close on outside click
O(document).on('click', () => {
O('.dropdown-menu').slideUp(200);
O('.dropdown-toggle').removeClass('active');
});
}
};
dropdown.init();
Modal Dialog
const modal = {
open(content, title = '') {
// Create modal if doesn't exist
if (!document.querySelector('#modal')) {
O('body').append(\`
<div id="modal-overlay"></div>
<div id="modal">
<div class="modal-header">
<h2 id="modal-title"></h2>
<button class="modal-close">Γ</button>
</div>
<div class="modal-body" id="modal-body"></div>
</div>
\`);
// Close handlers
O('#modal-overlay, .modal-close').on('click', () => {
this.close();
});
}
// Set content
O('#modal-title').text(title);
O('#modal-body').html(content);
// Show with animation
O('#modal-overlay').fadeIn(200);
O('#modal').css({ display: 'block', opacity: '0', transform: 'scale(0.8)' });
// Animate in
O('#modal').fadeIn(300);
},
close() {
O('#modal').fadeOut(200);
O('#modal-overlay').fadeOut(200);
}
};
// Usage
O('.show-modal').on('click', () => {
modal.open('<p>π Welcome to our onigiri shop!</p>', 'Welcome');
});
Accordion
const accordion = {
init() {
O('.accordion-header').on('click', function() {
const content = O(this).siblings('.accordion-content');
const isOpen = O(this).hasClass('open');
// Close all sections
O('.accordion-header')
.removeClass('open')
.siblings('.accordion-content')
.slideUp(300);
// Open clicked section (if it was closed)
if (!isOpen) {
O(this).addClass('open');
content.slideDown(300);
}
});
}
};
// HTML Structure
/*
<div class="accordion-item">
<div class="accordion-header">
π What is Onigiri?
</div>
<div class="accordion-content">
Onigiri is a Japanese rice ball...
</div>
</div>
*/
accordion.init();
Toast Messages
const toast = {
show(message, duration = 3000) {
const toastEl = document.createElement('div');
toastEl.className = 'toast';
toastEl.innerHTML = \`
<div class="toast-content">
π \${message}
</div>
\`;
document.body.appendChild(toastEl);
// Position at bottom
const wrapped = O(toastEl);
wrapped.css({
position: 'fixed',
bottom: '20px',
right: '20px',
background: '#333',
color: 'white',
padding: '15px 20px',
borderRadius: '8px',
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
display: 'none'
});
// Fade in
wrapped.fadeIn(300);
// Auto-hide
setTimeout(() => {
wrapped.fadeOut(300, function() {
this.remove();
});
}, duration);
}
};
// Usage
toast.show('Order placed successfully!');
toast.show('Item added to cart', 2000);
Loading Spinner
const loader = {
show(message = 'Loading...') {
if (document.querySelector('#loader')) {
return; // Already showing
}
O('body').append(\`
<div id="loader-overlay"></div>
<div id="loader">
<div class="spinner">π</div>
<p>\${message}</p>
</div>
\`);
O('#loader-overlay').fadeIn(200);
O('#loader').fadeIn(300);
},
hide() {
O('#loader').fadeOut(200);
O('#loader-overlay').fadeOut(200, function() {
O('#loader, #loader-overlay').remove();
});
}
};
// Usage
async function saveData() {
loader.show('Saving your onigiri order...');
try {
await Onigiri.post('/api/save', data);
toast.show('Saved successfully! π');
} catch (error) {
toast.show('Save failed');
} finally {
loader.hide();
}
}
Image Gallery
const gallery = {
init() {
O('.gallery-image').on('click', function() {
const src = O(this).data('full');
gallery.show(src);
});
},
show(imageSrc) {
// Create lightbox
O('body').append(\`
<div id="lightbox">
<div class="lightbox-overlay"></div>
<div class="lightbox-content">
<img src="\${imageSrc}" alt="π">
<button class="lightbox-close">Γ</button>
</div>
</div>
\`);
// Animate in
O('#lightbox .lightbox-overlay').fadeIn(200);
O('#lightbox .lightbox-content').css({
opacity: '0',
transform: 'scale(0.8)'
}).fadeIn(300);
// Close handlers
O('.lightbox-close, .lightbox-overlay').on('click', () => {
gallery.close();
});
},
close() {
O('#lightbox .lightbox-content').fadeOut(200);
O('#lightbox .lightbox-overlay').fadeOut(200, function() {
O('#lightbox').remove();
});
}
};
gallery.init();
Collapsible Sections
O('.collapsible-trigger').on('click', function() {
const target = O(this).data('target');
const content = O(target);
const isCollapsed = content.css('display') === 'none';
if (isCollapsed) {
content.slideDown(300);
O(this).text('Hide β²');
} else {
content.slideUp(300);
O(this).text('Show βΌ');
}
});
// HTML
/*
<button class="collapsible-trigger" data-target="#section1">
Show Details βΌ
</button>
<div id="section1" style="display:none;">
<p>Hidden content here...</p>
</div>
*/
Animated Counter
function animateCounter(element, start, end, duration) {
let current = start;
const increment = (end - start) / (duration / 16); // 60fps
const el = O(element);
function updateCounter() {
current += increment;
if ((increment > 0 && current >= end) ||
(increment < 0 && current <= end)) {
el.text(Math.round(end));
return;
}
el.text(Math.round(current));
requestAnimationFrame(updateCounter);
}
updateCounter();
}
// Usage
animateCounter('#onigiri-count', 0, 1000, 2000);
// Animates from 0 to 1000 over 2 seconds
Progressive Reveal
function revealElements(selector, delay = 100) {
const elements = O(selector).elements;
elements.forEach((el, index) => {
O(el).css({ opacity: '0', display: 'none' });
setTimeout(() => {
O(el).fadeIn(400);
}, index * delay);
});
}
// Usage
revealElements('.onigiri-card', 150);
// Cards appear one by one with 150ms delay
Shake Animation
function shake(element, duration = 500) {
const el = O(element).elements[0];
const originalTransform = el.style.transform;
el.animate([
{ transform: 'translateX(0)' },
{ transform: 'translateX(-10px)' },
{ transform: 'translateX(10px)' },
{ transform: 'translateX(-10px)' },
{ transform: 'translateX(10px)' },
{ transform: 'translateX(0)' }
], {
duration: duration,
easing: 'ease-in-out'
});
}
// Usage - shake on error
O('#submit-btn').on('click', async () => {
try {
await submitForm();
} catch (error) {
shake('#error-message');
O('#error-message').text('Failed! π');
}
});
Animation Chaining
// Sequential animations
O('.message')
.fadeIn(300, function() {
// After fade in completes
setTimeout(() => {
O(this).fadeOut(300, function() {
// After fade out completes
O(this).remove();
});
}, 2000);
});
// Complex sequence
async function animateSequence() {
// Step 1: Fade in
await new Promise(resolve => {
O('.step1').fadeIn(300, resolve);
});
// Step 2: Slide down
await new Promise(resolve => {
O('.step2').slideDown(400, resolve);
});
// Step 3: Fade in
await new Promise(resolve => {
O('.step3').fadeIn(300, resolve);
});
console.log('π Animation sequence complete!');
}
animateSequence();
π Performance Tip: OnigiriJS animations use requestAnimationFrame for smooth, 60fps animations that respect the browser's rendering cycle!
Animation Best Practices
- Keep animations fast (200-400ms for most UI transitions)
- Use callbacks for sequential animations
- Don't animate too many elements simultaneously
- Provide user control (skip, pause animations)
- Test on slower devices
- Use CSS animations for repeated effects
- Clean up (remove elements after fadeOut)
- Consider reduced motion preferences
Accessibility Considerations
// Respect prefers-reduced-motion
function shouldAnimate() {
return !window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}
// Conditional animation
if (shouldAnimate()) {
O('.message').fadeIn(300);
} else {
O('.message').show(); // Instant
}
// Or reduce duration
const duration = shouldAnimate() ? 300 : 0;
O('.message').fadeIn(duration);
β Development Roadmap
Track the progress of OnigiriJS modules. Tasks are marked complete by the development team.
OnigiriJS Module Roadmap
Implementation progress of planned modules
6 / 21 completed (29%)
onigiri-state
Shared global & scoped state management
onigiri-directives
Declarative DOM bindings (o-show, o-model, etc.)
onigiri-resource
REST-style data models over AJAX
onigiri-observe
Intersection & Mutation observer helpers
onigiri-humhub-ui
Standard HumHub UI abstractions (modal, notify, confirm)
onigiri-lifecycle
Component lifecycle hooks
onigiri-guard
Debounce, throttle, single-run guards
onigiri-scroll
Scroll save/restore & helpers (PJAX-friendly)
onigiri-permission
Client-side permission awareness
onigiri-portal
DOM teleport / overlay mounting
onigiri-router
Micro router (non-SPA, PJAX-first)
onigiri-sanitize
HTML & input sanitization
onigiri-shortcut
Keyboard shortcut manager
onigiri-queue
Sequential async task runner
onigiri-gesture
Touch & swipe helpers
onigiri-devtools
Debugging & inspection helpers
onigiri-plugin
Plugin registration system
onigiri-time
Relative time & timezone utilities
onigiri-emojis
Emoji Picker and Manager
onigiri-tasks
Task Management
onigiri-polls
Polls creation and management
Note: Task completion is managed by the OnigiriJS development team.