jQuery Animations and Effects
Learning Objectives
- Understand JavaScript fundamentals
- Add interactivity to web pages
- Manipulate page elements dynamically
- Handle user interactions
Session Overview
Welcome to our session on jQuery animations and effects! Today, we'll explore how jQuery makes it easy to create engaging, interactive user experiences through animations. By the end of this session, you'll understand how to implement various animation techniques that you can apply to your WordPress themes and plugins for a more polished, professional feel.
Why Animations Matter
Animations aren't just visual candy - they serve important purposes in user experience design:
- User Feedback: Animations provide visual feedback that actions have been registered
- Attention Direction: Guide users' focus to important elements or changes
- Context Preservation: Help users understand transitions between states
- Loading Indicators: Keep users informed during asynchronous operations
- Professional Polish: Create a more refined, premium feel for your applications
The Conversation Analogy
Think of animations as the body language and facial expressions of your website. Just as these nonverbal cues make human conversation smoother and more engaging, good animations enhance digital interaction. Without them, a website can feel as stiff and unnatural as talking to someone who maintains a blank expression and monotone voice.
In WordPress development, animations can make the difference between a site that feels amateur and one that feels professional. When used judiciously, they enhance usability without being distracting.
Basic jQuery Effects
jQuery provides several built-in methods for simple effects that are easy to implement yet powerful.
Visibility Effects: Show and Hide
// Basic show/hide (instant)
$("#element").hide(); // Hide an element
$("#element").show(); // Show a hidden element
// With animation duration (milliseconds)
$("#element").hide(500); // Fade out over 500ms
$("#element").show(1000); // Fade in over 1000ms
// With duration and callback function
$("#element").hide(300, function() {
// This code runs after the element is fully hidden
console.log("Element is now hidden");
});
// Toggle visibility (switch between shown and hidden)
$("#element").toggle(); // Instant toggle
$("#element").toggle(500); // Animated toggle
Real-World Example: WordPress Admin Notice
Here's how you might use show/hide effects to make WordPress admin notices dismissible:
jQuery(document).ready(function($) {
// Add a dismiss button to admin notices
$(".notice").each(function() {
$(this).append('<button type="button" class="notice-dismiss"><span class="screen-reader-text">Dismiss this notice</span></button>');
});
// When the dismiss button is clicked
$(".notice-dismiss").on("click", function() {
let $notice = $(this).closest(".notice");
// Animate the notice height to collapse it
$notice.animate({
opacity: 0,
height: 0,
margin: 0,
padding: 0
}, 300, function() {
// Remove from DOM after animation completes
$(this).hide();
// If this is a persistent notice, save the dismissal
if ($notice.data('notice-id')) {
$.post(ajaxurl, {
action: 'dismiss_admin_notice',
notice_id: $notice.data('notice-id'),
nonce: adminLocalVars.nonce
});
}
});
});
});
Sliding Effects
// Slide down (expand) an element
$("#panel").slideDown(); // Default duration
$("#panel").slideDown(500); // 500ms duration
// Slide up (collapse) an element
$("#panel").slideUp(); // Default duration
$("#panel").slideUp(300); // 300ms duration
// Toggle between slide up and slide down
$("#panel").slideToggle(); // Default duration
$("#panel").slideToggle(400); // 400ms duration
// With callback function
$("#panel").slideDown(500, function() {
// This code runs after the slide down completes
$(this).addClass("active");
});
The Curtain Analogy
Think of slideDown and slideUp like theater curtains. slideDown is like opening the curtains to reveal content, while slideUp is like closing them to hide content. Just as curtains provide a graceful transition between scenes, these effects provide a smooth way to show or hide content without jarring the user.
Real-World Example: WordPress Accordion Menu
Here's how you might implement an accordion menu for WordPress category navigation:
jQuery(document).ready(function($) {
// Initially hide all submenu items
$(".widget_categories ul.children").hide();
// Add toggle buttons to categories with children
$(".widget_categories li.cat-item:has(ul.children)").each(function() {
$(this).addClass("has-children").prepend('<span class="toggle-button">+</span>');
});
// When a toggle button is clicked
$(".widget_categories .toggle-button").on("click", function(e) {
e.preventDefault();
let $button = $(this);
let $category = $button.parent();
let $children = $category.find("> ul.children");
// Toggle the submenu with sliding effect
$children.slideToggle(300, function() {
// Update the toggle button text based on visibility
if ($(this).is(":visible")) {
$button.text("-");
$category.addClass("expanded");
} else {
$button.text("+");
$category.removeClass("expanded");
}
});
});
// If URL contains a specific category hash, expand it
if (window.location.hash) {
let categoryId = window.location.hash.substring(1);
let $targetCategory = $("#category-" + categoryId);
if ($targetCategory.length) {
// Expand all parent categories
$targetCategory.parents("li.has-children").each(function() {
$(this).find("> ul.children").slideDown(300);
$(this).find("> .toggle-button").text("-");
$(this).addClass("expanded");
});
// Scroll to the category
$('html, body').animate({
scrollTop: $targetCategory.offset().top - 50
}, 500);
}
}
});
Fading Effects
// Fade in (from transparent to visible)
$("#element").fadeIn(); // Default duration
$("#element").fadeIn(500); // 500ms duration
// Fade out (from visible to transparent)
$("#element").fadeOut(); // Default duration
$("#element").fadeOut(300); // 300ms duration
// Toggle between fade in and fade out
$("#element").fadeToggle(); // Default duration
$("#element").fadeToggle(400); // 400ms duration
// Fade to a specific opacity
$("#element").fadeTo(300, 0.5); // Fade to 50% opacity over 300ms
$("#element").fadeTo(600, 1); // Fade to 100% opacity over 600ms
// With callback function
$("#element").fadeIn(500, function() {
// This code runs after the fade in completes
$(this).css("border", "1px solid red");
});
Real-World Example: WordPress Image Gallery
Here's how you might create a simple lightbox for a WordPress gallery using fade effects:
jQuery(document).ready(function($) {
// Create the lightbox container if it doesn't exist
if ($("#image-lightbox").length === 0) {
$("body").append(`
<div id="image-lightbox" style="display:none;">
<div class="lightbox-overlay"></div>
<div class="lightbox-container">
<img src="" alt="" class="lightbox-image">
<div class="lightbox-caption"></div>
<button type="button" class="lightbox-close">×</button>
<button type="button" class="lightbox-prev"><<</button>
<button type="button" class="lightbox-next">>></button>
</div>
</div>
`);
}
// Keep track of current gallery and image index
let currentGallery = [];
let currentIndex = 0;
// When a gallery image is clicked
$(".gallery-item a").on("click", function(e) {
e.preventDefault();
// Get all images in this gallery
let $gallery = $(this).closest(".gallery");
currentGallery = $gallery.find(".gallery-item a").map(function() {
return {
src: $(this).attr("href"),
caption: $(this).attr("data-caption") || ""
};
}).get();
// Find the index of the clicked image
currentIndex = $gallery.find(".gallery-item a").index(this);
// Show the image in the lightbox
showLightboxImage(currentIndex);
});
// Show an image in the lightbox
function showLightboxImage(index) {
if (!currentGallery[index]) return;
let imageData = currentGallery[index];
let $lightbox = $("#image-lightbox");
let $image = $lightbox.find(".lightbox-image");
// If lightbox is not already open, fade it in
if (!$lightbox.is(":visible")) {
// Preload the image
let preloadImg = new Image();
preloadImg.onload = function() {
// Update image source and caption
$image.attr("src", imageData.src);
$lightbox.find(".lightbox-caption").text(imageData.caption);
// Fade in the lightbox
$lightbox.fadeIn(300);
};
preloadImg.src = imageData.src;
} else {
// Lightbox is already open, crossfade to new image
$image.fadeOut(200, function() {
$(this).attr("src", imageData.src).fadeIn(200);
$lightbox.find(".lightbox-caption").text(imageData.caption);
});
}
// Update current index
currentIndex = index;
// Enable/disable prev/next buttons
$lightbox.find(".lightbox-prev").prop("disabled", index === 0);
$lightbox.find(".lightbox-next").prop("disabled", index === currentGallery.length - 1);
}
// Handle lightbox navigation
$("#image-lightbox .lightbox-next").on("click", function() {
if (currentIndex < currentGallery.length - 1) {
showLightboxImage(currentIndex + 1);
}
});
$("#image-lightbox .lightbox-prev").on("click", function() {
if (currentIndex > 0) {
showLightboxImage(currentIndex - 1);
}
});
// Close the lightbox
$("#image-lightbox .lightbox-close, #image-lightbox .lightbox-overlay").on("click", function() {
$("#image-lightbox").fadeOut(300);
});
// Keyboard navigation
$(document).on("keydown", function(e) {
if (!$("#image-lightbox").is(":visible")) return;
switch(e.keyCode) {
case 27: // Escape key
$("#image-lightbox").fadeOut(300);
break;
case 37: // Left arrow
if (currentIndex > 0) {
showLightboxImage(currentIndex - 1);
}
break;
case 39: // Right arrow
if (currentIndex < currentGallery.length - 1) {
showLightboxImage(currentIndex + 1);
}
break;
}
});
});
Custom Animations with animate()
While the basic effects are useful, jQuery's animate() method gives you full control over custom animations.
The animate() Method
// Basic syntax
$("#element").animate({
// CSS properties to animate
property1: value1,
property2: value2,
// ...
}, duration, easing, callback);
// Simple example: animate width and height
$("#box").animate({
width: "300px",
height: "200px"
}, 500);
// Multiple properties with different units
$("#element").animate({
width: "50%",
height: "+=100px", // Relative values with +=/-=
opacity: 0.8,
fontSize: "1.2em",
marginLeft: "20px"
}, 800);
// With easing and callback
$("#element").animate({
left: "200px",
top: "100px"
}, 600, "swing", function() {
console.log("Animation complete!");
$(this).addClass("moved");
});
Animatable Properties
Not all CSS properties can be animated with jQuery. Here are the main categories:
- Dimensions: width, height, maxHeight, maxWidth, minHeight, minWidth
- Position: left, right, top, bottom
- Margins: margin, marginTop, marginRight, marginBottom, marginLeft
- Paddings: padding, paddingTop, paddingRight, paddingBottom, paddingLeft
- Borders: borderWidth, borderTopWidth, borderRightWidth, borderBottomWidth, borderLeftWidth
- Opacity: opacity
- Font Size: fontSize
- Line Height: lineHeight
- Word Spacing: wordSpacing
- Letter Spacing: letterSpacing
The Puppet Master Analogy
Think of the animate() method as a puppet master. Just as a puppet master controls specific parts of a puppet using strings (pulling an arm up, turning the head), animate() lets you control specific CSS properties of an element. You can't make the puppet do things it wasn't designed for (like bend its knees backward), and similarly, you can't animate CSS properties that jQuery doesn't support.
Animation Configuration Options
// Using configuration object
$("#element").animate({
width: "300px",
height: "200px"
}, {
duration: 600,
easing: "linear", // Default options: "swing" and "linear"
queue: true, // Whether to queue the animation
specialEasing: {
// Different easing for different properties
width: "swing",
height: "linear"
},
step: function(now, fx) {
// Called for each step of the animation
console.log(fx.prop + ": " + now);
},
complete: function() {
// Called when the animation is complete
console.log("Animation finished");
},
start: function() {
// Called when the animation starts
console.log("Animation started");
},
progress: function(animation, progress, remainingMs) {
// Called during the animation
console.log("Progress: " + progress);
}
});
Real-World Example: WordPress Sidebar Panel
Here's how you might create an animated sidebar panel for a WordPress theme:
jQuery(document).ready(function($) {
// Add the sidebar toggle button
$("#main-content").before('<button id="sidebar-toggle" class="sidebar-toggle-btn"><span class="dashicons dashicons-menu"></span></button>');
// Initial sidebar state
let sidebarOpen = true;
let sidebarWidth = $("#sidebar").width();
let windowWidth = $(window).width();
// On small screens, start with sidebar closed
if (windowWidth < 768) {
$("#sidebar").css("margin-left", -sidebarWidth);
$("#main-content").css("margin-left", 0);
sidebarOpen = false;
$("#sidebar-toggle").addClass("closed");
}
// When the toggle button is clicked
$("#sidebar-toggle").on("click", function() {
if (sidebarOpen) {
// Close the sidebar
$("#sidebar").animate({
marginLeft: -sidebarWidth
}, {
duration: 300,
easing: "swing",
queue: false
});
// Expand the content area
$("#main-content").animate({
marginLeft: 0
}, {
duration: 300,
easing: "swing",
queue: false,
complete: function() {
$(window).trigger("resize"); // Trigger resize for any responsive elements
}
});
// Update toggle button
$(this).addClass("closed");
sidebarOpen = false;
} else {
// Open the sidebar
$("#sidebar").animate({
marginLeft: 0
}, {
duration: 300,
easing: "swing",
queue: false
});
// Shrink the content area
$("#main-content").animate({
marginLeft: sidebarWidth
}, {
duration: 300,
easing: "swing",
queue: false,
complete: function() {
$(window).trigger("resize"); // Trigger resize for any responsive elements
}
});
// Update toggle button
$(this).removeClass("closed");
sidebarOpen = true;
}
});
// Handle window resize
$(window).on("resize", function() {
windowWidth = $(window).width();
// Update sidebar width
sidebarWidth = $("#sidebar").width();
// Adjust layout for small screens
if (windowWidth < 768 && sidebarOpen) {
// Close sidebar on small screens
$("#sidebar").css("margin-left", -sidebarWidth);
$("#main-content").css("margin-left", 0);
$("#sidebar-toggle").addClass("closed");
sidebarOpen = false;
} else if (windowWidth >= 768 && !sidebarOpen) {
// Open sidebar on larger screens
$("#sidebar").css("margin-left", 0);
$("#main-content").css("margin-left", sidebarWidth);
$("#sidebar-toggle").removeClass("closed");
sidebarOpen = true;
}
});
});
Chaining Animations
jQuery makes it easy to chain animations for more complex effects.
Method Chaining
// Basic animation chaining
$("#element")
.fadeIn(500)
.delay(200)
.animate({ width: "300px" }, 400)
.slideUp(300);
// Using callback functions for sequential animations
$("#element").fadeIn(500, function() {
$(this).animate({ width: "300px" }, 400, function() {
$(this).slideUp(300);
});
});
The Animation Queue
jQuery maintains a queue of animations for each element. By default, animations are added to this queue and played sequentially.
// Explicit queue management
$("#element")
.animate({ width: "300px" }, 500) // First animation
.queue(function(next) {
// This code runs between animations
$(this).addClass("resized");
next(); // Important! Continue to the next animation
})
.animate({ height: "200px" }, 500); // Second animation
// Queue manipulation
$("#element")
.delay(1000) // Delay before first animation
.animate({ width: "300px" }, 500) // First animation
.clearQueue() // Clear remaining animations
.animate({ height: "200px" }, 500, { queue: false }); // Run immediately (no queue)
The Movie Scene Analogy
Think of animation chaining like filming scenes for a movie. In the standard approach (queued animations), you film one scene, then the next, in sequence. But sometimes, you want multiple scenes happening at the same time (non-queued animations). The director (jQuery) can choose to film scenes sequentially or simultaneously, depending on the desired effect.
Stopping Animations
// Stop all animations on an element
$("#element").stop();
// Stop all animations and clear the queue
$("#element").stop(true);
// Stop current animation, jump to end, and clear the queue
$("#element").stop(true, true);
// More specific control
$("#element")
.animate({ width: "300px" }, 1000)
.animate({ height: "200px" }, 1000);
// Later, if you need to stop
$("#stop-button").on("click", function() {
// Stop only the current animation, continue with the next
$("#element").stop(false, false);
// Stop the current animation, jump to its end, then continue
$("#element").stop(false, true);
// Stop all animations, don't jump to end
$("#element").stop(true, false);
// Stop all animations and jump to end of current animation
$("#element").stop(true, true);
// Stop only animations on a specific property
$("#element").stop("width");
// Finish all animations immediately
$("#element").finish();
});
Real-World Example: WordPress Image Carousel
Here's how you might create an animated image carousel for a WordPress theme using animation chaining:
jQuery(document).ready(function($) {
// Initialize carousel
function initializeCarousel() {
let $carousel = $(".featured-carousel");
if ($carousel.length === 0) return;
let $slides = $carousel.find(".carousel-slide");
let slideCount = $slides.length;
let currentSlide = 0;
let slideWidth = $slides.first().outerWidth();
let slideInterval;
let autoPlayEnabled = $carousel.data("autoplay") !== false;
let autoPlaySpeed = $carousel.data("speed") || 5000;
// Hide all slides except the first one
$slides.not(":first").css("opacity", 0);
// Add navigation if there's more than one slide
if (slideCount > 1) {
// Add navigation dots
let $navDots = $("<div class='carousel-dots'></div>");
for (let i = 0; i < slideCount; i++) {
let $dot = $("<button type='button' class='carousel-dot'></button>");
if (i === 0) $dot.addClass("active");
$dot.data("slide", i);
$navDots.append($dot);
}
$carousel.append($navDots);
// Add prev/next buttons
$carousel.append(`
<button type="button" class="carousel-nav carousel-prev"><span class="dashicons dashicons-arrow-left-alt"></span></button>
<button type="button" class="carousel-nav carousel-next"><span class="dashicons dashicons-arrow-right-alt"></span></button>
`);
// Start autoplay if enabled
if (autoPlayEnabled) {
startAutoPlay();
}
// Pause autoplay on hover
$carousel.on("mouseenter", function() {
clearInterval(slideInterval);
}).on("mouseleave", function() {
if (autoPlayEnabled) {
startAutoPlay();
}
});
}
// Navigate to a specific slide
function goToSlide(index) {
if (index === currentSlide) return;
// Calculate direction for animation
let direction = index > currentSlide ? 1 : -1;
// Get current and next slides
let $currentSlide = $slides.eq(currentSlide);
let $nextSlide = $slides.eq(index);
// Stop any current animations
$slides.stop(true, true);
// Position the next slide
$nextSlide.css({
opacity: 0,
transform: `translateX(${direction * 50}px)`
});
// Animate current slide out
$currentSlide.animate({
opacity: 0,
transform: `translateX(${-direction * 50}px)`
}, 500, function() {
$(this).css("z-index", 1);
});
// Animate next slide in
$nextSlide.css("z-index", 2).animate({
opacity: 1,
transform: "translateX(0)"
}, 500);
// Update current slide index
currentSlide = index;
// Update active dot
$carousel.find(".carousel-dot").removeClass("active").eq(currentSlide).addClass("active");
}
// Go to the next slide
function nextSlide() {
let nextIndex = (currentSlide + 1) % slideCount;
goToSlide(nextIndex);
}
// Go to the previous slide
function prevSlide() {
let prevIndex = (currentSlide - 1 + slideCount) % slideCount;
goToSlide(prevIndex);
}
// Start autoplay
function startAutoPlay() {
clearInterval(slideInterval);
slideInterval = setInterval(nextSlide, autoPlaySpeed);
}
// Event handlers for navigation
$carousel.on("click", ".carousel-next", function() {
nextSlide();
// Reset autoplay timer
if (autoPlayEnabled) {
clearInterval(slideInterval);
startAutoPlay();
}
});
$carousel.on("click", ".carousel-prev", function() {
prevSlide();
// Reset autoplay timer
if (autoPlayEnabled) {
clearInterval(slideInterval);
startAutoPlay();
}
});
$carousel.on("click", ".carousel-dot", function() {
let slideIndex = $(this).data("slide");
goToSlide(slideIndex);
// Reset autoplay timer
if (autoPlayEnabled) {
clearInterval(slideInterval);
startAutoPlay();
}
});
// Handle swipe gestures on touch devices
let touchStartX = 0;
let touchEndX = 0;
$carousel.on("touchstart", function(e) {
touchStartX = e.originalEvent.touches[0].clientX;
});
$carousel.on("touchend", function(e) {
touchEndX = e.originalEvent.changedTouches[0].clientX;
handleSwipe();
});
function handleSwipe() {
let swipeThreshold = 50;
if (touchStartX - touchEndX > swipeThreshold) {
// Swipe left - next slide
nextSlide();
} else if (touchEndX - touchStartX > swipeThreshold) {
// Swipe right - previous slide
prevSlide();
}
// Reset autoplay timer
if (autoPlayEnabled) {
clearInterval(slideInterval);
startAutoPlay();
}
}
}
// Initialize all carousels on the page
initializeCarousel();
});
Timing and Easing Functions
Easing functions control how animations progress over time, adding a natural feel to your animations.
Built-in Easing Functions
jQuery comes with two basic easing functions:
- linear: Animation progresses at a constant speed from start to finish
- swing (default): Animation starts slowly, speeds up in the middle, and slows down at the end
// Linear easing
$("#element").animate({
width: "300px"
}, 500, "linear");
// Swing easing (default)
$("#element").animate({
width: "300px"
}, 500, "swing");
jQuery UI Easing Functions
For more easing options, you can include jQuery UI or just the jQuery UI effects core:
// Include jQuery UI in WordPress
function enqueue_jquery_ui() {
wp_enqueue_script('jquery-ui-effects', array('jquery'));
}
add_action('wp_enqueue_scripts', 'enqueue_jquery_ui');
// Then you can use more easing functions
$("#element").animate({
width: "300px"
}, 500, "easeOutBounce");
// Available easing functions with jQuery UI:
// easeInQuad, easeOutQuad, easeInOutQuad
// easeInCubic, easeOutCubic, easeInOutCubic
// easeInQuart, easeOutQuart, easeInOutQuart
// easeInQuint, easeOutQuint, easeInOutQuint
// easeInSine, easeOutSine, easeInOutSine
// easeInExpo, easeOutExpo, easeInOutExpo
// easeInCirc, easeOutCirc, easeInOutCirc
// easeInElastic, easeOutElastic, easeInOutElastic
// easeInBack, easeOutBack, easeInOutBack
// easeInBounce, easeOutBounce, easeInOutBounce
The Vehicle Analogy
Think of easing functions like different vehicles. Linear easing is like a train that moves at a constant speed from station to station. Swing easing is like a car that accelerates from a stop, cruises, then decelerates to stop. Bounce easing is like a ball that bounces before coming to rest. Each creates a different feel for your animation, just as different vehicles create different travel experiences.
Implementing Custom Easing Functions
// If you don't want to include all of jQuery UI,
// you can implement custom easing functions:
// Add a custom easing function
$.easing.customEasing = function(x, t, b, c, d) {
// x: current position (0-1)
// t: current time
// b: beginning value
// c: change in value
// d: duration
// Example: Cubic easing
return c * (t /= d) * t * t + b;
};
// Use the custom easing function
$("#element").animate({
width: "300px"
}, 500, "customEasing");
Real-World Example: WordPress Form Submission Feedback
Here's how you might use different easing functions to provide visual feedback in a WordPress contact form:
jQuery(document).ready(function($) {
// Add jQuery UI effects if needed
if (typeof $.easing.easeOutBounce === 'undefined') {
// Add a simple implementation of the bounce effect
// This is a simplified version; consider including jQuery UI for production
$.easing.easeOutBounce = function(x) {
const n1 = 7.5625;
const d1 = 2.75;
if (x < 1 / d1) {
return n1 * x * x;
} else if (x < 2 / d1) {
return n1 * (x -= 1.5 / d1) * x + 0.75;
} else if (x < 2.5 / d1) {
return n1 * (x -= 2.25 / d1) * x + 0.9375;
} else {
return n1 * (x -= 2.625 / d1) * x + 0.984375;
}
};
}
// Handle form submission
$(".contact-form").on("submit", function(e) {
e.preventDefault();
let $form = $(this);
let $submitBtn = $form.find("input[type='submit']");
let formData = $form.serialize();
// Disable submit button and show loading
$submitBtn.prop("disabled", true).val("Sending...");
// Add a loading indicator
if ($form.find(".form-loader").length === 0) {
$form.append("<div class='form-loader'><div class='loader-spinner'></div></div>");
}
$(".form-loader").fadeIn(300);
// Send the AJAX request
$.ajax({
url: ajax_object.ajax_url,
type: "POST",
data: {
action: "submit_contact_form",
nonce: ajax_object.nonce,
form_data: formData
},
success: function(response) {
// Hide loader
$(".form-loader").fadeOut(300);
if (response.success) {
// Show success message with bounce effect
let $successMsg = $("<div class='form-message success'>" + response.data.message + "</div>");
$form.slideUp(400, function() {
$(this).after($successMsg);
$successMsg.css({
opacity: 0,
transform: "translateY(-20px)"
}).animate({
opacity: 1,
transform: "translateY(0)"
}, 500, "easeOutBounce");
});
} else {
// Show error message with shake effect
let $errorMsg = $("<div class='form-message error'>" + response.data.message + "</div>");
$form.find(".form-messages").html($errorMsg);
// Shake effect
let startX = parseInt($form.css("margin-left")) || 0;
$form.animate({ marginLeft: startX - 10 }, 100)
.animate({ marginLeft: startX + 10 }, 100)
.animate({ marginLeft: startX - 10 }, 100)
.animate({ marginLeft: startX + 10 }, 100)
.animate({ marginLeft: startX }, 100);
// Re-enable submit button
$submitBtn.prop("disabled", false).val("Submit");
}
},
error: function() {
// Hide loader
$(".form-loader").fadeOut(300);
// Show error message
let $errorMsg = $("<div class='form-message error'>An error occurred. Please try again later.</div>");
$form.find(".form-messages").html($errorMsg);
// Fade in/out error message to draw attention
$errorMsg.fadeIn(300).delay(200).fadeOut(300).delay(200).fadeIn(300);
// Re-enable submit button
$submitBtn.prop("disabled", false).val("Submit");
}
});
});
});
Animation Utilities and Controls
jQuery provides several utilities to fine-tune your animations.
The delay() Method
// Add a 1-second delay between animations
$("#element")
.fadeIn(300)
.delay(1000) // 1000ms delay
.slideUp(300);
// Delay only applies to animations in the queue
$("#element")
.delay(1000)
.css("background-color", "red"); // This happens immediately, not after delay
// To delay non-animation methods, use setTimeout
$("#element").fadeIn(300);
setTimeout(function() {
$("#element").css("background-color", "red");
}, 1000);
The jQuery.fx Object
This global object controls animation settings for all jQuery animations:
// Turn off all animations site-wide (useful for testing)
$.fx.off = true; // All animations complete immediately
// Later, turn animations back on
$.fx.off = false;
// Change the default animation duration (default is 400ms)
$.fx.speeds._default = 200; // Make all animations faster by default
// Access/set preset animation speeds
$.fx.speeds.slow = 800; // Default is 600
$.fx.speeds.fast = 200; // Default is 200
// Use preset speeds in animations
$("#element").fadeIn("slow");
$("#element").fadeOut("fast");
Feature Detection and Fallbacks
// Check if CSS transitions are supported
function supportsTransitions() {
let b = document.body || document.documentElement;
let s = b.style;
return typeof s.transition !== 'undefined' ||
typeof s.WebkitTransition !== 'undefined' ||
typeof s.MozTransition !== 'undefined' ||
typeof s.OTransition !== 'undefined';
}
// Use CSS transitions if supported, fallback to jQuery animations
if (supportsTransitions()) {
$("#element").addClass("fade-in"); // CSS class with transition
} else {
$("#element").fadeIn(300); // jQuery fallback
}
// Modern approach using feature queries in CSS
// @supports (transition: opacity 0.3s) {
// .fade-in {
// opacity: 0;
// transition: opacity 0.3s;
// }
// .fade-in.active {
// opacity: 1;
// }
// }
Real-World Example: WordPress Loading States
Here's how you might implement loading states with animation controls in a WordPress admin page:
jQuery(document).ready(function($) {
// Global settings for admin animations
// Check if reduced motion is preferred
let prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
// Respect user's motion preferences by making animations instant
$.fx.off = true;
}
// Define animation speed based on admin settings
let animationSpeed = wpAdminSettings.animation_speed || 'normal';
switch (animationSpeed) {
case 'fast':
$.fx.speeds.normal = 200;
break;
case 'slow':
$.fx.speeds.normal = 600;
break;
case 'none':
$.fx.off = true;
break;
default:
$.fx.speeds.normal = 400; // Default
}
// Custom loading state function
function showLoading($element, message) {
let $loader;
message = message || "Loading...";
// If there's already a loader, update its message
if ($element.data("has-loader")) {
$loader = $element.next(".admin-loader");
$loader.find(".loader-message").text(message);
return $loader;
}
// Create loader
$loader = $(`
<div class="admin-loader">
<div class="loader-spinner"></div>
<div class="loader-message">${message}</div>
</div>
`);
// Mark element as having a loader
$element.data("has-loader", true);
// Insert loader after element and show it
$element.after($loader);
if (!$.fx.off) {
// Animated loading if animations are enabled
$loader.css({ opacity: 0, height: 0 })
.animate({ opacity: 1, height: $loader.get(0).scrollHeight }, "normal");
// Add a subtle pulse animation to the element being loaded
$element.addClass("is-loading");
} else {
// Instant if animations are disabled
$loader.css({ opacity: 1 });
$element.addClass("is-loading");
}
return $loader;
}
// Hide loading state
function hideLoading($element, success) {
if (!$element.data("has-loader")) return;
let $loader = $element.next(".admin-loader");
// Show success/error feedback briefly before hiding
if (success === true) {
$loader.addClass("success").find(".loader-message").text("Complete!");
} else if (success === false) {
$loader.addClass("error").find(".loader-message").text("Error!");
}
// Remove loading state
$element.removeClass("is-loading");
if (!$.fx.off) {
// Animated hiding if animations are enabled
$loader.delay(success !== undefined ? 1000 : 0)
.animate({ opacity: 0, height: 0 }, "normal", function() {
$(this).remove();
$element.data("has-loader", false);
});
} else {
// Instant if animations are disabled
if (success !== undefined) {
setTimeout(function() {
$loader.remove();
$element.data("has-loader", false);
}, 1000);
} else {
$loader.remove();
$element.data("has-loader", false);
}
}
}
// Example usage:
$("#refresh-button").on("click", function() {
let $button = $(this);
// Disable button and show loading
$button.prop("disabled", true);
showLoading($button, "Refreshing data...");
// Simulate AJAX call
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'refresh_dashboard_data',
nonce: wpAdminVars.nonce
},
success: function(response) {
if (response.success) {
// Update UI with new data
$("#dashboard-data").html(response.data.html);
hideLoading($button, true);
} else {
hideLoading($button, false);
alert(response.data.message || "An error occurred");
}
},
error: function() {
hideLoading($button, false);
alert("Connection error. Please try again.");
},
complete: function() {
$button.prop("disabled", false);
}
});
});
// Toggle sections with animation
$(".section-toggle").on("click", function() {
let $toggle = $(this);
let $section = $toggle.next(".toggle-section");
if ($section.is(":visible")) {
$toggle.removeClass("open");
if (!$.fx.off) {
$section.slideUp("normal");
} else {
$section.hide();
}
} else {
$toggle.addClass("open");
if (!$.fx.off) {
$section.slideDown("normal");
} else {
$section.show();
}
}
});
});
Advanced Animation Techniques
CSS vs. jQuery Animations
Modern web development often uses CSS animations for better performance. Here's how to combine both approaches:
// CSS for animations
/*
.animated-element {
transition: all 0.3s ease-in-out;
}
.animated-element.active {
transform: scale(1.1);
background-color: #f0f0f0;
}
*/
// jQuery to trigger CSS animations
$("#element").on("click", function() {
$(this).toggleClass("active");
});
// Using jQuery to detect when CSS transitions end
$("#element").on("transitionend webkitTransitionEnd oTransitionEnd", function() {
console.log("CSS transition completed");
$(this).trigger("animation-complete");
});
// Hybrid approach: Use CSS for performance, jQuery for convenience
function animateElement($el) {
// Add class for CSS transition
$el.addClass("animating");
// After transition completes, do something
$el.one("transitionend webkitTransitionEnd oTransitionEnd", function() {
$el.removeClass("animating").addClass("animated");
});
}
Animating Along Paths
// Simple linear path animation
function animateAlongPath($element, points, duration) {
let startTime = null;
function animate(timestamp) {
if (!startTime) startTime = timestamp;
let progress = (timestamp - startTime) / duration;
if (progress > 1) progress = 1;
// Get current point on the path
let pointIndex = Math.floor(progress * (points.length - 1));
let pointProgress = progress * (points.length - 1) - pointIndex;
// Interpolate between points
let x, y;
if (pointIndex < points.length - 1) {
x = points[pointIndex].x + (points[pointIndex + 1].x - points[pointIndex].x) * pointProgress;
y = points[pointIndex].y + (points[pointIndex + 1].y - points[pointIndex].y) * pointProgress;
} else {
x = points[points.length - 1].x;
y = points[points.length - 1].y;
}
// Update element position
$element.css({
left: x + 'px',
top: y + 'px'
});
if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
}
// Example usage
let path = [
{ x: 100, y: 100 },
{ x: 200, y: 50 },
{ x: 300, y: 150 },
{ x: 400, y: 100 }
];
animateAlongPath($("#moving-element"), path, 2000); // 2 seconds
Synchronized Animations
// Animate multiple elements in sync
function syncAnimate(elements, properties, options) {
let $elements = $(elements);
let promises = [];
// Create a promise for each element's animation
$elements.each(function() {
let $el = $(this);
let deferred = $.Deferred();
$el.animate(properties, {
duration: options.duration || 400,
easing: options.easing || "swing",
complete: function() {
deferred.resolve();
}
});
promises.push(deferred.promise());
});
// Return a master promise that resolves when all animations complete
return $.when.apply($, promises).promise();
}
// Example usage
syncAnimate(".panel", {
opacity: 1,
top: 0
}, {
duration: 500,
easing: "easeOutCirc"
}).done(function() {
console.log("All panels animated!");
});
Real-World Example: WordPress Form Submission Feedback
Here's how you might use different easing functions to provide visual feedback in a WordPress contact form:
jQuery(document).ready(function($) {
// Add jQuery UI effects if needed
if (typeof $.easing.easeOutBounce === 'undefined') {
// Add a simple implementation of the bounce effect
// This is a simplified version; consider including jQuery UI for production
$.easing.easeOutBounce = function(x) {
const n1 = 7.5625;
const d1 = 2.75;
if (x < 1 / d1) {
return n1 * x * x;
} else if (x < 2 / d1) {
return n1 * (x -= 1.5 / d1) * x + 0.75;
} else if (x < 2.5 / d1) {
return n1 * (x -= 2.25 / d1) * x + 0.9375;
} else {
return n1 * (x -= 2.625 / d1) * x + 0.984375;
}
};
}
// Handle form submission
$(".contact-form").on("submit", function(e) {
e.preventDefault();
let $form = $(this);
let $submitBtn = $form.find("input[type='submit']");
let formData = $form.serialize();
// Disable submit button and show loading
$submitBtn.prop("disabled", true).val("Sending...");
// Add a loading indicator
if ($form.find(".form-loader").length === 0) {
$form.append("<div class='form-loader'><div class='loader-spinner'></div></div>");
}
$(".form-loader").fadeIn(300);
// Send the AJAX request
$.ajax({
url: ajax_object.ajax_url,
type: "POST",
data: {
action: "submit_contact_form",
nonce: ajax_object.nonce,
form_data: formData
},
success: function(response) {
// Hide loader
$(".form-loader").fadeOut(300);
if (response.success) {
// Show success message with bounce effect
let $successMsg = $("<div class='form-message success'>" + response.data.message + "</div>");
$form.slideUp(400, function() {
$(this).after($successMsg);
$successMsg.css({
opacity: 0,
transform: "translateY(-20px)"
}).animate({
opacity: 1,
transform: "translateY(0)"
}, 500, "easeOutBounce");
});
} else {
// Show error message with shake effect
let $errorMsg = $("<div class='form-message error'>" + response.data.message + "</div>");
$form.find(".form-messages").html($errorMsg);
// Shake effect
let startX = parseInt($form.css("margin-left")) || 0;
$form.animate({ marginLeft: startX - 10 }, 100)
.animate({ marginLeft: startX + 10 }, 100)
.animate({ marginLeft: startX - 10 }, 100)
.animate({ marginLeft: startX + 10 }, 100)
.animate({ marginLeft: startX }, 100);
// Re-enable submit button
$submitBtn.prop("disabled", false).val("Submit");
}
},
error: function() {
// Hide loader
$(".form-loader").fadeOut(300);
// Show error message
let $errorMsg = $("<div class='form-message error'>An error occurred. Please try again later.</div>");
$form.find(".form-messages").html($errorMsg);
// Fade in/out error message to draw attention
$errorMsg.fadeIn(300).delay(200).fadeOut(300).delay(200).fadeIn(300);
// Re-enable submit button
$submitBtn.prop("disabled", false).val("Submit");
}
});
});
});
Animation Utilities and Controls
jQuery provides several utilities to fine-tune your animations.
The delay() Method
// Add a 1-second delay between animations
$("#element")
.fadeIn(300)
.delay(1000) // 1000ms delay
.slideUp(300);
// Delay only applies to animations in the queue
$("#element")
.delay(1000)
.css("background-color", "red"); // This happens immediately, not after delay
// To delay non-animation methods, use setTimeout
$("#element").fadeIn(300);
setTimeout(function() {
$("#element").css("background-color", "red");
}, 1000);
The jQuery.fx Object
This global object controls animation settings for all jQuery animations:
// Turn off all animations site-wide (useful for testing)
$.fx.off = true; // All animations complete immediately
// Later, turn animations back on
$.fx.off = false;
// Change the default animation duration (default is 400ms)
$.fx.speeds._default = 200; // Make all animations faster by default
// Access/set preset animation speeds
$.fx.speeds.slow = 800; // Default is 600
$.fx.speeds.fast = 200; // Default is 200
// Use preset speeds in animations
$("#element").fadeIn("slow");
$("#element").fadeOut("fast");
Feature Detection and Fallbacks
// Check if CSS transitions are supported
function supportsTransitions() {
let b = document.body || document.documentElement;
let s = b.style;
return typeof s.transition !== 'undefined' ||
typeof s.WebkitTransition !== 'undefined' ||
typeof s.MozTransition !== 'undefined' ||
typeof s.OTransition !== 'undefined';
}
// Use CSS transitions if supported, fallback to jQuery animations
if (supportsTransitions()) {
$("#element").addClass("fade-in"); // CSS class with transition
} else {
$("#element").fadeIn(300); // jQuery fallback
}
// Modern approach using feature queries in CSS
// @supports (transition: opacity 0.3s) {
// .fade-in {
// opacity: 0;
// transition: opacity 0.3s;
// }
// .fade-in.active {
// opacity: 1;
// }
// }
Real-World Example: WordPress Loading States
Here's how you might implement loading states with animation controls in a WordPress admin page:
jQuery(document).ready(function($) {
// Global settings for admin animations
// Check if reduced motion is preferred
let prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
// Respect user's motion preferences by making animations instant
$.fx.off = true;
}
// Define animation speed based on admin settings
let animationSpeed = wpAdminSettings.animation_speed || 'normal';
switch (animationSpeed) {
case 'fast':
$.fx.speeds.normal = 200;
break;
case 'slow':
$.fx.speeds.normal = 600;
break;
case 'none':
$.fx.off = true;
break;
default:
$.fx.speeds.normal = 400; // Default
}
// Custom loading state function
function showLoading($element, message) {
let $loader;
message = message || "Loading...";
// If there's already a loader, update its message
if ($element.data("has-loader")) {
$loader = $element.next(".admin-loader");
$loader.find(".loader-message").text(message);
return $loader;
}
// Create loader
$loader = $(`
<div class="admin-loader">
<div class="loader-spinner"></div>
<div class="loader-message">${message}</div>
</div>
`);
// Mark element as having a loader
$element.data("has-loader", true);
// Insert loader after element and show it
$element.after($loader);
if (!$.fx.off) {
// Animated loading if animations are enabled
$loader.css({ opacity: 0, height: 0 })
.animate({ opacity: 1, height: $loader.get(0).scrollHeight }, "normal");
// Add a subtle pulse animation to the element being loaded
$element.addClass("is-loading");
} else {
// Instant if animations are disabled
$loader.css({ opacity: 1 });
$element.addClass("is-loading");
}
return $loader;
}
// Hide loading state
function hideLoading($element, success) {
if (!$element.data("has-loader")) return;
let $loader = $element.next(".admin-loader");
// Show success/error feedback briefly before hiding
if (success === true) {
$loader.addClass("success").find(".loader-message").text("Complete!");
} else if (success === false) {
$loader.addClass("error").find(".loader-message").text("Error!");
}
// Remove loading state
$element.removeClass("is-loading");
if (!$.fx.off) {
// Animated hiding if animations are enabled
$loader.delay(success !== undefined ? 1000 : 0)
.animate({ opacity: 0, height: 0 }, "normal", function() {
$(this).remove();
$element.data("has-loader", false);
});
} else {
// Instant if animations are disabled
if (success !== undefined) {
setTimeout(function() {
$loader.remove();
$element.data("has-loader", false);
}, 1000);
} else {
$loader.remove();
$element.data("has-loader", false);
}
}
}
// Example usage:
$("#refresh-button").on("click", function() {
let $button = $(this);
// Disable button and show loading
$button.prop("disabled", true);
showLoading($button, "Refreshing data...");
// Simulate AJAX call
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'refresh_dashboard_data',
nonce: wpAdminVars.nonce
},
success: function(response) {
if (response.success) {
// Update UI with new data
$("#dashboard-data").html(response.data.html);
hideLoading($button, true);
} else {
hideLoading($button, false);
alert(response.data.message || "An error occurred");
}
},
error: function() {
hideLoading($button, false);
alert("Connection error. Please try again.");
},
complete: function() {
$button.prop("disabled", false);
}
});
});
// Toggle sections with animation
$(".section-toggle").on("click", function() {
let $toggle = $(this);
let $section = $toggle.next(".toggle-section");
if ($section.is(":visible")) {
$toggle.removeClass("open");
if (!$.fx.off) {
$section.slideUp("normal");
} else {
$section.hide();
}
} else {
$toggle.addClass("open");
if (!$.fx.off) {
$section.slideDown("normal");
} else {
$section.show();
}
}
});
});
Advanced Animation Techniques
CSS vs. jQuery Animations
Modern web development often uses CSS animations for better performance. Here's how to combine both approaches:
// CSS for animations
/*
.animated-element {
transition: all 0.3s ease-in-out;
}
.animated-element.active {
transform: scale(1.1);
background-color: #f0f0f0;
}
*/
// jQuery to trigger CSS animations
$("#element").on("click", function() {
$(this).toggleClass("active");
});
// Using jQuery to detect when CSS transitions end
$("#element").on("transitionend webkitTransitionEnd oTransitionEnd", function() {
console.log("CSS transition completed");
$(this).trigger("animation-complete");
});
// Hybrid approach: Use CSS for performance, jQuery for convenience
function animateElement($el) {
// Add class for CSS transition
$el.addClass("animating");
// After transition completes, do something
$el.one("transitionend webkitTransitionEnd oTransitionEnd", function() {
$el.removeClass("animating").addClass("animated");
});
}
Animating Along Paths
// Simple linear path animation
function animateAlongPath($element, points, duration) {
let startTime = null;
function animate(timestamp) {
if (!startTime) startTime = timestamp;
let progress = (timestamp - startTime) / duration;
if (progress > 1) progress = 1;
// Get current point on the path
let pointIndex = Math.floor(progress * (points.length - 1));
let pointProgress = progress * (points.length - 1) - pointIndex;
// Interpolate between points
let x, y;
if (pointIndex < points.length - 1) {
x = points[pointIndex].x + (points[pointIndex + 1].x - points[pointIndex].x) * pointProgress;
y = points[pointIndex].y + (points[pointIndex + 1].y - points[pointIndex].y) * pointProgress;
} else {
x = points[points.length - 1].x;
y = points[points.length - 1].y;
}
// Update element position
$element.css({
left: x + 'px',
top: y + 'px'
});
if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
}
// Example usage
let path = [
{ x: 100, y: 100 },
{ x: 200, y: 50 },
{ x: 300, y: 150 },
{ x: 400, y: 100 }
];
animateAlongPath($("#moving-element"), path, 2000); // 2 seconds
Synchronized Animations
// Animate multiple elements in sync
function syncAnimate(elements, properties, options) {
let $elements = $(elements);
let promises = [];
// Create a promise for each element's animation
$elements.each(function() {
let $el = $(this);
let deferred = $.Deferred();
$el.animate(properties, {
duration: options.duration || 400,
easing: options.easing || "swing",
complete: function() {
deferred.resolve();
}
});
promises.push(deferred.promise());
});
// Return a master promise that resolves when all animations complete
return $.when.apply($, promises).promise();
}
// Example usage
syncAnimate(".panel", {
opacity: 1,
top: 0
}, {
duration: 500,
easing: "easeOutCirc"
}).done(function() {
console.log("All panels animated!");
});
Real-World Example: WordPress Admin Dashboard Animation
Here's an advanced example of creating an animated dashboard for a WordPress plugin:
jQuery(document).ready(function($) {
// Initialize the dashboard
function initDashboard() {
// Cache selectors for performance
const $dashboard = $(".analytics-dashboard");
const $widgets = $dashboard.find(".dashboard-widget");
const $charts = $dashboard.find(".chart-container");
const $metrics = $dashboard.find(".metric-card");
// Check if dashboard exists
if ($dashboard.length === 0) return;
// Set up animation sequence
function animateDashboardLoad() {
// Reset animation state
$widgets.css({
opacity: 0,
transform: "translateY(20px)"
});
$metrics.css({
opacity: 0,
transform: "scale(0.9)"
});
// Stagger animations for a more dynamic effect
// First, animate the dashboard container
$dashboard.css({
opacity: 0
}).animate({
opacity: 1
}, 400, function() {
// Then animate header elements
$dashboard.find(".dashboard-header").css({
opacity: 0,
transform: "translateY(-20px)"
}).animate({
opacity: 1,
transform: "translateY(0)"
}, 400, "easeOutQuad");
// Then animate metrics with a staggered delay
$metrics.each(function(i) {
let $metric = $(this);
setTimeout(function() {
$metric.animate({
opacity: 1,
transform: "scale(1)"
}, 400, "easeOutBack");
}, 100 + (i * 100)); // Stagger by 100ms
});
// Then animate widgets with a staggered delay
$widgets.each(function(i) {
let $widget = $(this);
setTimeout(function() {
$widget.animate({
opacity: 1,
transform: "translateY(0)"
}, 500, "easeOutQuad", function() {
// When each widget is visible, animate its chart if it has one
let $widgetChart = $widget.find(".chart-container");
if ($widgetChart.length) {
animateChart($widgetChart);
}
});
}, 300 + (i * 150)); // Stagger by 150ms, start after metrics
});
});
}
// Animate an individual chart
function animateChart($chartContainer) {
let chartType = $chartContainer.data("chart-type");
let $canvas = $chartContainer.find("canvas");
if (!$canvas.length) return;
// Different animation based on chart type
switch(chartType) {
case "bar":
// For bar charts, animate the bars growing up
$canvas.css({ height: 0 })
.animate({ height: "100%" }, 600, "easeOutQuint");
break;
case "line":
// For line charts, use a CSS drawing animation
// This assumes you have a CSS animation for drawing SVG paths
$chartContainer.addClass("animate-line");
break;
case "pie":
// For pie charts, animate from 0 to the actual value
$chartContainer.addClass("animate-pie");
break;
default:
// Default fade-in animation
$canvas.css({ opacity: 0 })
.animate({ opacity: 1 }, 400);
}
// Trigger chart render (for libraries like Chart.js)
$chartContainer.trigger("chart-animate");
}
// Animate dashboard when data refreshes
function animateDataRefresh(newData) {
// Animate metrics changing
$metrics.each(function() {
let $metric = $(this);
let metricKey = $metric.data("metric-key");
if (!newData[metricKey]) return;
let oldValue = parseFloat($metric.find(".metric-value").text());
let newValue = newData[metricKey].value;
// Skip animation if values are the same
if (oldValue === newValue) return;
// Determine if value increased or decreased
let direction = newValue > oldValue ? "up" : "down";
$metric.addClass("change-" + direction);
// Animate the numeric value changing
$({ value: oldValue }).animate({
value: newValue
}, {
duration: 800,
easing: "easeOutCubic",
step: function() {
$metric.find(".metric-value").text(Math.round(this.value));
},
complete: function() {
// Ensure the final value is exactly correct
$metric.find(".metric-value").text(newValue);
// Remove direction class after animation
setTimeout(function() {
$metric.removeClass("change-up change-down");
}, 1000);
}
});
});
// Animate charts updating
$charts.each(function() {
let $chart = $(this);
let chartKey = $chart.data("chart-key");
if (!newData[chartKey]) return;
// Fade out and back in
$chart.animate({
opacity: 0.3
}, 300, function() {
// Update chart data (this would vary based on your chart library)
$chart.trigger("chart-update", [newData[chartKey]]);
// Fade back in
$chart.animate({
opacity: 1
}, 300);
});
});
}
// Setup dashboard interactions
function setupDashboardInteractions() {
// Widget drag and drop reordering
$dashboard.find(".widget-container").sortable({
handle: ".widget-header",
placeholder: "widget-placeholder",
animation: 300,
start: function(e, ui) {
// Add a class while dragging
ui.item.addClass("widget-dragging");
ui.placeholder.height(ui.item.height());
},
stop: function(e, ui) {
// Remove the dragging class
ui.item.removeClass("widget-dragging");
// Save the new widget order
let widgetOrder = [];
$dashboard.find(".dashboard-widget").each(function() {
widgetOrder.push($(this).data("widget-id"));
});
// Save via AJAX
$.post(ajaxurl, {
action: "save_widget_order",
widget_order: widgetOrder,
nonce: dashboardVars.nonce
});
}
});
// Widget expand/collapse
$dashboard.on("click", ".widget-toggle", function() {
let $widget = $(this).closest(".dashboard-widget");
let $content = $widget.find(".widget-content");
if ($widget.hasClass("collapsed")) {
// Expand
$widget.removeClass("collapsed");
$(this).find("span").removeClass("dashicons-arrow-down").addClass("dashicons-arrow-up");
$content.slideDown(300, function() {
// Trigger chart resize if there's a chart
$widget.find(".chart-container").trigger("chart-resize");
});
} else {
// Collapse
$widget.addClass("collapsed");
$(this).find("span").removeClass("dashicons-arrow-up").addClass("dashicons-arrow-down");
$content.slideUp(300);
}
// Save state
$.post(ajaxurl, {
action: "save_widget_state",
widget_id: $widget.data("widget-id"),
collapsed: $widget.hasClass("collapsed"),
nonce: dashboardVars.nonce
});
});
// Date range selector animation
$dashboard.find(".date-range-selector").on("click", function() {
let $selector = $(this);
let $dropdown = $selector.find(".date-range-dropdown");
if ($dropdown.is(":visible")) {
$dropdown.slideUp(200);
} else {
$dropdown.slideDown(200);
}
});
// Refreshing data
$dashboard.find(".refresh-button").on("click", function() {
let $button = $(this);
// Disable button and show loading animation
$button.prop("disabled", true);
$button.find(".dashicons").addClass("spin-animation");
// Dim the dashboard during loading
$dashboard.addClass("loading");
// Fetch new data
$.ajax({
url: ajaxurl,
type: "POST",
data: {
action: "fetch_dashboard_data",
date_range: $dashboard.find(".date-range-active").data("range"),
nonce: dashboardVars.nonce
},
success: function(response) {
if (response.success) {
// Animate the data refreshing
animateDataRefresh(response.data);
} else {
// Show error
alert(response.data.message || "Error fetching data");
}
},
error: function() {
alert("Connection error. Please try again.");
},
complete: function() {
// Remove loading state
$button.prop("disabled", false);
$button.find(".dashicons").removeClass("spin-animation");
$dashboard.removeClass("loading");
}
});
});
}
// Initialize the dashboard
animateDashboardLoad();
setupDashboardInteractions();
}
// Call dashboard initialization when the page loads
initDashboard();
});
Animation Best Practices for WordPress
Performance Considerations
- Minimize DOM manipulations: Batch changes to avoid layout thrashing
- Use CSS transforms and opacity: These properties are GPU-accelerated
- Be mindful of mobile devices: Test animations on lower-powered devices
- Respect user preferences: Check prefers-reduced-motion media query
- Consider alternatives to jQuery: Modern browsers support native animations
// Check for reduced motion preference
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// Adjust animations accordingly
if (prefersReducedMotion) {
// Disable animations or use simplified versions
$.fx.off = true; // Turn off jQuery animations
// Or use simpler animations
function animateElement($el) {
// Just change state without animation
$el.addClass("active");
}
} else {
// Use full animations
function animateElement($el) {
$el.fadeIn(300).animate({
opacity: 1,
transform: "scale(1)"
}, 400, "easeOutBack");
}
}
UX Guidelines for Animations
- Purpose-driven: Every animation should serve a clear purpose
- Subtle is better: Avoid flashy, distracting animations
- Keep it fast: Most UI animations should be 200-500ms
- Consistent: Use similar animations for similar actions
- Natural feeling: Use easing functions that mimic natural motion
- Provide feedback: Use animations to confirm user actions
- State transitions: Smooth transitions between UI states
The Seasoning Analogy
Think of animations like seasoning in cooking. Used properly, they enhance the experience without overwhelming it. Too little, and your interface feels bland and lifeless. Too much, and it becomes garish and inedible. The perfect amount adds just enough flavor to make the experience delightful without calling attention to itself.
WordPress-Specific Animation Guidelines
- Follow WordPress UI patterns: Maintain consistency with core animations
- Progressively enhance: Ensure functionality works without animations
- Consider accessibility: Animations can cause issues for some users
- Test on various devices: Ensure animations work well across all target devices
- Optimize for performance: WordPress sites often have many plugins that may impact performance
Practical Applications in WordPress
Example 1: Animated Notification System
Here's how to create an animated notification system for your WordPress theme or plugin:
// Function to show notifications
function showNotification(message, type, duration) {
// Default parameters
type = type || 'info'; // info, success, warning, error
duration = duration || 4000; // 4 seconds
// Create notification element if it doesn't exist
let $notificationArea = $("#notification-area");
if ($notificationArea.length === 0) {
$notificationArea = $("<div id='notification-area'></div>");
$("body").append($notificationArea);
}
// Create notification
let $notification = $("<div class='notification notification-" + type + "'>" +
"<div class='notification-message'>" + message + "</div>" +
"<button class='notification-close'>×</button>" +
"</div>");
// Add to notification area
$notificationArea.append($notification);
// Initially hide the notification
$notification.css({
opacity: 0,
transform: "translateY(-20px)"
});
// Show with animation
$notification.animate({
opacity: 1,
transform: "translateY(0)"
}, 300, "easeOutCubic");
// Set up auto-dismissal
let dismissTimeout = setTimeout(function() {
dismissNotification($notification);
}, duration);
// Close button handler
$notification.find(".notification-close").on("click", function() {
clearTimeout(dismissTimeout);
dismissNotification($notification);
});
// Return the notification object for further manipulation
return $notification;
}
// Function to dismiss notifications
function dismissNotification($notification) {
$notification.animate({
opacity: 0,
transform: "translateY(-20px)"
}, 300, "easeInCubic", function() {
// Remove from DOM after animation
$(this).remove();
});
}
// Example usage in WordPress
$(document).ready(function($) {
// Show notification when a form is submitted successfully
$(".ajax-form").on("submit", function(e) {
e.preventDefault();
let $form = $(this);
let formData = $form.serialize();
// Show loading state
let $submitButton = $form.find("input[type='submit']");
let originalButtonText = $submitButton.val();
$submitButton.prop("disabled", true).val("Sending...");
// Send AJAX request
$.ajax({
url: ajax_vars.url,
type: "POST",
data: formData + "&action=process_form&nonce=" + ajax_vars.nonce,
success: function(response) {
if (response.success) {
// Show success notification
showNotification(response.data.message, "success");
// Reset form
$form[0].reset();
} else {
// Show error notification
showNotification(response.data.message || "An error occurred", "error");
}
},
error: function() {
// Show connection error notification
showNotification("Connection error. Please try again.", "error");
},
complete: function() {
// Restore button state
$submitButton.prop("disabled", false).val(originalButtonText);
}
});
});
// Show notification for WordPress admin notices
$(".is-dismissible").each(function() {
let $notice = $(this);
let noticeType = "info";
if ($notice.hasClass("notice-success")) {
noticeType = "success";
} else if ($notice.hasClass("notice-warning")) {
noticeType = "warning";
} else if ($notice.hasClass("notice-error")) {
noticeType = "error";
}
// Get notice text
let noticeText = $notice.find("p").first().text();
// Hide the original notice
$notice.hide();
// Show our custom notification
showNotification(noticeText, noticeType);
});
});
Example 2: Content Reveal on Scroll
Implementing a scroll-triggered animation to reveal content as the user scrolls down the page:
// Function to check if element is in viewport
function isElementInViewport($element, offset) {
offset = offset || 100; // Default offset from bottom of viewport
let windowHeight = $(window).height();
let windowScrollTop = $(window).scrollTop();
let elementTop = $element.offset().top;
// Element is in viewport if its top is less than viewport bottom minus offset
return elementTop < (windowScrollTop + windowHeight - offset);
}
// Function to animate elements when they enter the viewport
function animateOnScroll() {
$(".animate-on-scroll").each(function() {
let $element = $(this);
// Skip if already animated
if ($element.hasClass("animated")) return;
// Check if in viewport
if (isElementInViewport($element)) {
// Determine animation type
let animationType = $element.data("animation") || "fade";
// Apply animation
switch (animationType) {
case "fade":
$element.css({
opacity: 0
}).animate({
opacity: 1
}, 800);
break;
case "slide-up":
$element.css({
opacity: 0,
transform: "translateY(50px)"
}).animate({
opacity: 1,
transform: "translateY(0)"
}, 800);
break;
case "slide-right":
$element.css({
opacity: 0,
transform: "translateX(-50px)"
}).animate({
opacity: 1,
transform: "translateX(0)"
}, 800);
break;
case "slide-left":
$element.css({
opacity: 0,
transform: "translateX(50px)"
}).animate({
opacity: 1,
transform: "translateX(0)"
}, 800);
break;
case "zoom-in":
$element.css({
opacity: 0,
transform: "scale(0.8)"
}).animate({
opacity: 1,
transform: "scale(1)"
}, 800);
break;
}
// Mark as animated
$element.addClass("animated");
}
});
}
// Initialize scroll animations
$(document).ready(function($) {
// Run once on page load
animateOnScroll();
// Run on scroll with throttling for performance
let scrollTimeout;
$(window).on("scroll", function() {
if (scrollTimeout) {
clearTimeout(scrollTimeout);
}
scrollTimeout = setTimeout(function() {
animateOnScroll();
}, 100);
});
// Add scroll animation to WordPress content elements
if ($(".entry-content").length) {
// Add animation to headings
$(".entry-content h2, .entry-content h3").addClass("animate-on-scroll").attr("data-animation", "slide-right");
// Add animation to paragraphs
$(".entry-content p").addClass("animate-on-scroll").attr("data-animation", "fade");
// Add animation to images
$(".entry-content img").addClass("animate-on-scroll").attr("data-animation", "zoom-in");
// Add animation to lists
$(".entry-content ul, .entry-content ol").addClass("animate-on-scroll").attr("data-animation", "slide-left");
// Run animation check again
animateOnScroll();
}
});
Example 3: Animated WordPress Login Page
Customizing the WordPress login page with animations:
// In your theme's functions.php or plugin file
function enqueue_login_scripts() {
wp_enqueue_script('jquery');
wp_enqueue_script('login-animations', get_template_directory_uri() . '/js/login-animations.js', array('jquery'), '1.0', true);
wp_enqueue_style('login-styles', get_template_directory_uri() . '/css/login-styles.css');
}
add_action('login_enqueue_scripts', 'enqueue_login_scripts');
// JavaScript file (login-animations.js)
jQuery(document).ready(function($) {
// Cache DOM elements
let $loginForm = $("#loginform");
let $logo = $(".login h1");
let $loginError = $("#login_error");
let $message = $(".message");
// Initially hide all elements
$loginForm.css("opacity", 0);
$logo.css("opacity", 0);
if ($loginError.length) $loginError.css("opacity", 0);
if ($message.length) $message.css("opacity", 0);
// Animate logo first
$logo.animate({
opacity: 1,
top: 0
}, 800, "easeOutQuad", function() {
// Then animate form
$loginForm.animate({
opacity: 1,
top: 0
}, 600, "easeOutQuad", function() {
// Focus on username field
$("#user_login").focus();
// Animate error message if present
if ($loginError.length) {
$loginError.animate({
opacity: 1,
left: 0
}, 400, "easeOutQuad");
}
// Animate info message if present
if ($message.length) {
$message.animate({
opacity: 1,
right: 0
}, 400, "easeOutQuad");
}
});
});
// Add animation to form fields when focused
$("#loginform input").on("focus", function() {
$(this).addClass("input-focus");
}).on("blur", function() {
$(this).removeClass("input-focus");
});
// Animate submit button on hover
$("#wp-submit").on("mouseenter", function() {
$(this).stop().animate({
backgroundColor: "#0073e6"
}, 300);
}).on("mouseleave", function() {
$(this).stop().animate({
backgroundColor: "#2271b1"
}, 300);
});
// Add loading animation when form is submitted
$loginForm.on("submit", function() {
// Disable submit button
let $submitBtn = $("#wp-submit");
$submitBtn.prop("disabled", true);
// Add and animate loading indicator
if ($submitBtn.find(".loading-spinner").length === 0) {
$submitBtn.append("<span class='loading-spinner'></span>");
}
// Fade form slightly to indicate processing
$loginForm.animate({
opacity: 0.7
}, 300);
// Form will submit normally
return true;
});
});
Homework Assignment
To practice your jQuery animation skills, complete the following tasks:
-
Basic Animation Challenge:
Create a WordPress plugin that adds an animated "Back to Top" button. The button should:
- Appear when the user scrolls down the page
- Disappear when the user is at the top
- Smoothly scroll the page to the top when clicked
- Have hover and active state animations
-
Intermediate Animation Challenge:
Create an animated image gallery for WordPress with the following features:
- Thumbnails that expand with an animation when clicked
- Next/previous navigation with slide transitions
- A caption area that animates in from the bottom
- A loading animation while images are being fetched
-
Advanced Animation Challenge:
Create a custom WordPress admin widget that displays site statistics with animated charts. Requirements:
- At least three different types of animated charts (bar, line, pie)
- Data that animates when it changes (e.g., counters that increment)
- Interactive elements that respond with animations (e.g., tooltips, filters)
- Staggered animation sequence when the widget first loads
For each challenge, ensure your solution follows the animation best practices we discussed, including:
- Respecting reduced motion preferences
- Using appropriate timing and easing functions
- Providing proper visual feedback
- Considering performance implications
Submit your completed code via GitHub before the next session. Include a README with explanations of your animation choices and any challenges you faced.
Additional Resources
Documentation and Tutorials
- jQuery Effects Documentation
- jQuery UI Effects
- jQuery Learning Center: Effects
- CSS-Tricks: Ease Functions in JavaScript
- Cubic Bezier Generator
Animation Libraries for WordPress
- Animate.css: Ready-to-use CSS animations that work well with jQuery
- GSAP (GreenSock Animation Platform): Professional-grade animation library
- Velocity.js: Accelerated jQuery animations
- AOS (Animate On Scroll): Trigger animations when elements enter viewport
- Lottie: For complex vector animations