v1.0.0

🎨 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.