Animate Search and Text Placeholders

Animated placeholder text reveal tutorial

Add text-reveal animations to input, search and text area placeholders on your WordPress site with this short copy-and-paste tutorial. It’s a neat UX enhancement for pages where you want to draw the user’s attention to an input box.

To follow this tutorial, make sure your site is using a custom child theme so you have somewhere you can add the code snippets.

We’re going to make this

Scaffold the code

This project will contain two source files.

  1. PHP file to control which content will have the animated effect.
    You might want to disable it for your site’s front page, or maybe on your checkout page.
  2. A JavaScript file that does the actual work of animating the text.

In your child theme, create a file called wpt-fancy-input-placeholders.php and paste the following in to it.

<?php

/**
 * Headwall WP Tutorials : Fancy Input Placeholders (WPTFIP)
 *
 * https://wp-tutorials.tech/refine-wordpress/animate-search-and-text-placeholders/
 */

// Block direct access
defined('WPINC') || die();

const WPTFIP_ANIMATION_DELAY = 300;
const WPTFIP_ANIMATION_INTERVAL = 60;
const WPTFIP_DEFAULT_SELECTOR = 'input[placeholder], textarea[placeholder]';

/**
 * Return true on any page-load that needs the animated placeholder effect.
 * By default, it will be available on all pages. Hook the
 * are_animated_input_placeholders_enabled filter to enable/disable per-page.
 */
function wptfip_is_enabled() {
	return (bool)apply_filters('are_animated_input_placeholders_enabled', true);
}

/**
 * Enqueue our JS asset and set its operating parameters in the global
 * "wptfip" JS variable. If you want the effect to be available on specific
 * elements, hook the wptfip_element_selector filter to set the element
 * selector, e.g. '.text-input' or 'input[type="search"]'.
 */
function wptfip_enqueue_scripts() {
	if (!wptfip_is_enabled()) {
		// Not enabled on this page-load.
	} else {
		$base_url = get_stylesheet_directory_uri();
		$version = wp_get_theme()->get('Version');
		$handle = 'wptfip';
		$selector = apply_filters('wptfip_element_selector', WPTFIP_DEFAULT_SELECTOR);

		// We want our script to execute in the footer, as soon as possible.
		wp_enqueue_script(
			$handle,
			$base_url . '/wpt-fancy-input-placeholders.js',
			null, // Our script has no dependencies
			$version,
			[
				'in_footer' => true,
			]
		);

		wp_localize_script(
			$handle,
			'wptfip', // This will be the name of our global JS object 
			[
				'selector' => $selector,
				'animationDelay' => WPTFIP_ANIMATION_DELAY,
				'animationInterval' => WPTFIP_ANIMATION_INTERVAL,
			]
		);
	}
}
add_filter('wp_enqueue_scripts', 'wptfip_enqueue_scripts');

Next, open your child theme’s functions.php file and add the following couple of lines.

// Headwall WP Tutorials : Fancy Input Placeholders
require_once dirname(__FILE__) . '/wpt-fancy-input-placeholders.php';

Now create an empty file for the JavaScript called “wpt-fancy-input-placeholders.js” and save it.

Reload your site to check that nothing has broken.

The PHP function wptfip_enqueue_scripts() should be easy to read through, and it breaks down like this:

  1. If we need to enqueue our code on this page-load, then…
    • Set up our variables and get the version number of our child theme
    • Set the $selector variable using a sensible default, then rinse it through a filter so it can be changed at runtime
    • Enqueue the JS file with wp_enqueue_script(). In this case, we want to enqueue it in the HTML footer so it can run immediately, without waiting for the document to load and become “ready”
    • Call wp_localize_script() to inject the global JS object wptfip, which contains our runtime properties

Create an input with a placeholder

Now we need some content to animate. Create a page or a post and add a search box to it. Publish the content and check how the search box is rendered in HTML.

If you use the WordPress Block Editor to build your content, your block will look like this

WordPress search with animated placeholder text
Search Block rendered as HTML
WordPress Search Block as HTML in the browser

By using input[placeholder], textarea[placeholder] as our element selector, we will pick up the search box and any other INTPUT or TEXTAREA boxes that have a placeholder set.

The core JavaScript code

The logic for the JavaScript code will work like this:

  1. Initialise
    • For each element in the DOM that matches our selector…
      • Create a meta object that contains a link to the DOM element, and the original placeholder text
      • Set the element’s actual placeholder text to an empty string
    • If we found one or more elements to animate, then…
      • Wait a short time
      • Call the animation function to animate the first frame
  2. Animate frame…
    • For each meta object…
      • If the meta object placeholder text is not yet empty, then…
        • Copy the first character of the meta object’s placeholder text to the end of the element’s actual placeholder text
        • Remove the first character from the meta object’s placeholder text
    • If we animated one or more elements on this iteration, then…
      • Wait for a short time
      • Call the animation function again, using setTimeout()

Open wpt-fancy-input-placeholders.js and paste the following code into it:

/**
 * Headwall WP Tutorials Fancy Input Placeholders (WPTFIP)
 *
 * https://wp-tutorials.tech/refine-wordpress/animate-search-and-text-placeholders/
 */

// Diagnostics
// console.log('wptfip : load');

if (typeof wptfip === 'undefined') {
  // We should never end up in here because wptfip should always
  // be set by wp_localize_script()
} else {
  /**
   * Scan for all elements that need to be animated, and set up the
   * meta objects to track the element animations.
   */
  wptfip.init = () => {
    // Diagnostics
    // console.log(`wptfip : init`);
    // console.log(`wptfip : ${wptfip.selector}`);

    // This array will hold simple "meta" objects. Each one will contain a
    // link to an aniamted element, and the original placehodler text.
    wptfip.elementMetas = [];

    document.querySelectorAll(wptfip.selector).forEach((element) => {
      // Create a meta object with a link to our element,
      // and copy its placeholder text into it.
      wptfip.elementMetas.push({
        element: element,
        realPlaceholder: element.placeholder,
      });

      // Set the element's placehodler text to an empty string.
      element.placeholder = '';
    });

    // Only proceed if we found some elements to animate.
    if (wptfip.elementMetas.length > 0) {
      // Diagnostics
      // console.log('Ready to call the first animation frame');

      window.setTimeout(
        wptfip.nextFrame, // Our main animation function
        wptfip.animationDelay, // Wait for a short time before animating the first frame
      );
    }
  };

  /**
   * This is our main animation function, and it is called each time we want
   * to animate a frame (i.e. add a new letter to the placeholder text).
   *
   * If no more elements need their placehodler text updating (i.e. they are
   * all visible now) then we can finish.
   */
  wptfip.nextFrame = () => {
    // How many elements are being animated on this iteration.
    let animatedCount = 0;

    wptfip.elementMetas.forEach((elementMeta) => {
      if (elementMeta.realPlaceholder.length > 0) {
        // Copy the next letter of the placeholder text into the element's placeholder.
        elementMeta.element.placeholder += elementMeta.realPlaceholder[0];

        // Remove the first character of the original placehodler text.
        elementMeta.realPlaceholder = elementMeta.realPlaceholder.substring(1);

        ++animatedCount;
      }
    });

    if (animatedCount == 0) {
      // Diagnostics
      // console.log('Finished animating fancy placeholders');
    } else {
      window.setTimeout(wptfip.nextFrame, wptfip.animationInterval);
    }
  };

  /**
   * Our main entry point.
   */
  wptfip.init();
}

Save that and reload your page’s content. The holder text show now have the text-reveal effect.

If you want to see the code “working”, uncomment the console.log(…) lines and open your browser’s Dev Tools panel.

Wrapping up

To make it easy to use this code usable in multiple projects, you want avoid making changes to WPTFIP_DEFAULT_SELECTOR at the top of the PHP file. So what we’ve done is wrap it up in a filter so we can use different selectors in each of our projects.

To hook the filter, add the following snippet to your child theme’s functions.php file and change the selector to suit your project.

/**
 * Only use the text-reveal placeholder animation on search boxes,
 * not all text input boxes
 */
function custom_wptfip_element_selector($selector) {
	$selector = 'input[type="search"][placeholder]';

	return $selector;
}
add_filter('wptfip_element_selector', 'custom_wptfip_element_selector', 10, 1);

That’s all there is to it – a fun bit of JavaScript bling for your WordPress projects 😎 👍

Like This Tutorial?

Let us know

WordPress plugins for developers

Leave a comment