Creating a slick, animated Full Screen Search Form with CSS3 and JavaScript
Created: Feb 8th, 17'
A well designed and accessible search UI encourages users to interact with your site's search function more frequently, leading to higher user satisfaction and increased page views. To that end, and with the dominance of small screen devices, the trend for search boxes has been to make them extremely large and bold. In this tutorial, we'll go over step by step how to create a slick, well engineered full screen search form that works beautifully across all modern browsers and devices.
Creating the header and search icon interface
To begin, we'll quickly put together a header section using the classic pattern of a logo on the left, and some navigational links plus the search icon on the far right, with everything centered vertically. Eventually clicking on the icon is what will trigger the full screen search box. Instead of using CSS floats, we'll turn to CSS flexbox instead to achieve this layout in a succinct, future proof manner:
Figure #1: Header with 3 flex items inside it
The HTML:
<header> <a id="logo" href="http://www.javascriptkit.com"><img src="jklogosmall.gif" /></a> <ul> <li><a href="#">Item 1</a></li> <li><a href="#">Item 2</a></li> <li><a href="#">Item 3</a></li> <li><a href="#">Item 4</a></li> </ul> <label for="search-terms" id="search-label"><img src="search.png" /></label> </header>
The CSS:
body{ padding-top: 70px; // set top padding to house the fixed header } header{ position: fixed; width: 100%; display: flex; top: 0; left: 0; justify-content: space-between; /* space flex items evenly horizontally */ align-items: center; /* center flex items vertically */ background: white; padding: 10px 5px; font: bold 16px 'Bitter', sans-serif; /* use google font */ } header, header *, form#search, form#search *{ -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } header ul{ list-style: none; margin: 0; padding: 0; margin-left: auto; /* right align this flex child */ margin-right: 10px; } header ul li{ display: inline; } header ul li a{ text-decoration: none; padding: 5px; } header #search-label{ cursor: pointer; display: flex; justify-content: center; align-items: center; /* center flex items inside vertically */ width: 60px; height: 40px; z-index: 100; }
The header is set to be fixed
in position and display:flex
,
which turns the 3 children elements inside - the logo, the UL menu, and
the search box label - into flex elements, making it effortless to
position these elements the way we want to. Specifically, by adding justify-content:
space-between
to the header, the three child elements are spaced
horizontally apart evenly (we tweak the UL menu later to break from
this), and with align-items: center
, all the child elements
are vertically centered inside the header.
Our flex box settings at this point has the 3 header components spaced
apart equally on the horizontal axis, though personally I prefer the UL
menu to be right aligned next to the search box label. To do this in CSS
flex box, we just add margin-left: auto
to the menu UL.
More details on this technique here.
And finally, for the search box label, or the gateway to our full screen search form, we choose to use a label instead of a regular DIV or span, so when it's clicked on, focus is automatically set on the associated INPUT element. On mobile browsers, this behaviour also automatically triggers the mobile keyboard, which makes for good design. What may not be the best design choice is the use of an image for the "search" icon. When actually deploying you may want to opt for a SVG or icon font that scales without resolution loss, such as one from the excellent IcoMoon.
Creating the Full Screen Search Form
Now it's time for the real fun, creating the search form that's triggered by the search label we defined earlier:
Figure #2: Full Screen Search Form that pops up when "search" label
is clicked on
We want the form to appear when the user clicks (or taps in mobile browsers) on the search icon label, and to dismiss it when the user clicks on the label again, or anywhere outside the search field.
Lets see the markup, CSS and JavaScript that makes all this happen:
The HTML:
<div id="searchcontainer"> <form id="search" action="#" method="post"> <input type="text" name="search-terms" id="search-terms" placeholder="Enter search terms..."> <div>Press Enter to Search</div> </form> </div>
For the markup we define a "#searchcontainer
" DIV to contain
the search form and search INPUT field.
The CSS:
div#searchcontainer{ position: fixed; width: 100%; height: 100%; z-index: 100; display: block; background: purple; left: -100%; /* initially position search container out of view */ top: 90px; /* shift container downwards so the header is still visible when search is shown */ padding-top: 50px; opacity: 0; cursor: crosshair; text-align: center; font: bold 16px 'Bitter', sans-serif; /* use google font */ -webkit-transform: scale(.9) translate3d(-0, -50px, 0); transform: scale(.9) translate3d(-0, -50px, 0); -webkit-transition: -webkit-transform .5s, opacity .5s, left 0s .5s; transition: transform .5s, opacity .5s, left 0s .5s; } div#searchcontainer div{ padding: 5px; color: white; } div#searchcontainer form{ opacity: 0; -webkit-transform: translate3d(0, 50px, 0); transform: translate3d(0, 50px, 0); -webkit-transition: all .5s 0s; transition: all .5s 0s; } div#searchcontainer form input[type="text"]{ width: 90%; top: 0; left: 0; z-index: 99; padding: 10px; border: none; border-bottom: 2px solid gray; outline: none; font-size: 3em; background: #eee; } div#searchcontainer.opensearch{ left: 0; opacity: 1; -webkit-transform: scale(1) translate3d(0, 0, 0); transform: scale(1) translate3d(0, 0, 0); -webkit-transition: -webkit-transform .5s, opacity .5s, left 0s 0s; transition: transform .5s, opacity .5s, left 0s 0s; } div#searchcontainer.opensearch form{ opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); transition: all .5s .5s; transition: all .5s .5s; } @media (max-width: 480px){ div#searchcontainer form input[type="text"]{ width: 95%; } }
Lets break down the important bits in the CSS. For the #searchcontainer
DIV, we set it to "position:fixed
" with "top:90px
"
so it doesn't obscure the header element when visible while spanning the
rest of the page. And speaking of visibility, the DIV is initially
hidden by setting its opacity
to 0, but also in addition, its left
property to -100%
. This ensures that the DIV doesn't overlay (and
obstruct) the page when it's not visible on the screen. We didn't
opt for using "visibility:hidden
", as that prevents
any form INPUT inside it from receiving focus, which negates the benefit
of using a form LABEL element to trigger the search container's
visibility.
To toggle the CSS transitions that reveal and hide the search container
and search field, we dynamically add/ remove a CSS class "opensearch
"
to the #searchcontainer
DIV using JavaScript. Inside the "opensearch
"
class, we define two sets of transitions, one targeting the #searchcontainer
DIV, the other the INPUT field inside it. The two are run sequentially,
by making use of
transition-delay
inside the CSS
transition
shorthand property to stall the later transition. The first transition runs
immediately when activated, fading in and shifting downwards slightly
into its final resting page the #searchcontainer
DIV.
Notice the "left
" property is set to explicitly not
transition and without delay (left 0s 0s
), so the container
appears in the proper coordinates horizontally immediately when opened.
Contrast that with when its time to hide the container, by removing the
"opensearch
" class from it. The default transition
property settings inside #searchcontainer
stipulates that
the left
property in that case should also not transition,
but kick in after a delay of ".5s
" (left 0s .5s
)
into the declared value of "-100%
", so the other
transition properties such as "opacity
" and "transform
"
have time to play out first.
The second transition applies to the form inside #searchcontainer
(div#searchcontainer.opensearch form{}
), and kicks in after
the first to reveal the search INPUT itself.
The JavaScript:
var ismobile = navigator.userAgent.match(/(iPad)|(iPhone)|(iPod)|(android)|(webOS)/i) != null var touchorclick = (ismobile)? 'touchstart' : 'click' var searchcontainer = document.getElementById('searchcontainer') var searchfield = document.getElementById('search-terms') var searchlabel = document.getElementById('search-label') searchlabel.addEventListener(touchorclick, function(e){ // when user clicks on search label searchcontainer.classList.toggle('opensearch') // add or remove 'opensearch' to searchcontainer if (!searchcontainer.classList.contains('opensearch')){ // if hiding searchcontainer searchfield.blur() // blur search field e.preventDefault() // prevent default label behavior of focusing on search field again } e.stopPropagation() // stop event from bubbling upwards }, false) searchfield.addEventListener(touchorclick, function(e){ // when user clicks on search field e.stopPropagation() // stop event from bubbling upwards }, false) document.addEventListener(touchorclick, function(e){ // when user clicks anywhere in document searchcontainer.classList.remove('opensearch') searchfield.blur() }, false)
And last but certainly not least, we arrive at the JavaScript portion of
the script. The first line returns a Boolean indicating whether the User
Agent falls into one of the major mobile devices category. We use this
Boolean to decide whether to bind to the "click
" or "touchstart
"
event on the various elements. Mobile devices also support "click
"
any way should our Boolean fail to properly categorize a mobile device
in use, though "touchstart
" reacts more quickly, without
the infamous 300ms delay of "click
" on mobile devices.
When the user clicks on the search label inside the header element, we
toggle the visibility of the full screen search container by toggling
the CSS class "opensearch
" on it. If the current action is
to close the search container- by checking that the class "opensearch
"
is missing from the container - we blur the search INPUT field while
suppressing the default label action of setting focus on the element
from kicking in. The act of setting focus and blurring on a form field
has the added advantage of automatically bringing up and dismissing the
virtual keyboard on most mobile browsers.
To dismiss the search container when the user clicks anywhere on the
document other than on the label or the search field itself, we attach a
"click
/ touchstart
" event to the document
element that accomplishes this. Calling e.stopPropagation()
inside the search label and field prevents the same action inside these
elements from reaching the document.
And finally, our JavaScript makes use of the classList API to handle CSS classes, which is not supported in IE9 or lower. There's a good classList pollyfill if you need legacy browsers support.