Viewing front-end View all News

How-To: Uncle Goose Create-a-Name

An exciting part of last year’s Uncle Goose redesign was the “Create-a-Name” block generator. This had been the most user-requested feature, so it was important for us to make it happen.

Uncle Goose wanted a simple, intuitive way for customers to order individual blocks from one of three sets, and have each block’s color be customizable.

create-a-name-demo

Here’s a look at how we built it.

HTML

First, let’s get our container set up. We need a text input so people can type in the letters they want. We also have an empty div where our letters will be stored.

The class set-* will dictate which set style the letters have. The data-set attribute will store the set information for use in the shopping cart.

<div class="container letters-container">
	<input type="text" class="create-a-name-text" maxlength="26" id="create-a-name-text" placeholder="Enter text" autofocus />
	<div class="letters set-abc clear" data-set="set-abc"></div><!-- /.letters -->
</div><!-- /.container.letters-container -->

We also need a way for the customer to select which set they want:

<div class="set-selector">
 	<label>Set:</label>
	<select id="set-filter">
 		<option selected value="set-abc">Classic ABC</option>
 		<option value="set-upperlower">Upper Lower</option>
 		<option value="set-lower">Lowercase</option>
 	</select>
 </div><!-- /.set-selector -->

We also need to store all the info about which blocks and options the customer has selected. We’re going to use a link with all the data stored in data-* attributes that can be parsed by the cart on submission. Our cart needs a product ID, which is just the WordPress post ID, a block quantity to calculate the price, and all the letter, set, and color details are stored in a product note.

<a href="#" class="add-to-cart btn green icon-cart" data-id="<?=get_the_ID()?>" data-qty="0" data-note="">Add To Cart</a>

Let’s get a template going for our letters. The template letter will be hidden but will be used by our javascript to create new letters on the fly. This is neater and simpler than writing out all this HTML in the javascript. We’re also going to store the color and set information in data attributes so they’re easy to use in our javascript later on.

We’re using PHP to import the SVGs we need for two of the sets. The third set has a solid background color so we can use CSS alone to style it.

<!-- This block serves as a template for any blocks we dynamically create. -->
<div class="letter-wrapper letter-template" data-color="" data-letter="" style="display: none">
 	<div class="letter-inner">
 	 	<?php @include(dirname(__FILE__) . '/img/filigree-abc.svg'); ?>
 	 	<?php @include(dirname(__FILE__) . '/img/filigree-lower.svg'); ?>
 	 	<span class="letter"></span>
 	</div><!-- /.letter-inner -->

 	<div class="color-select">
 	 	<div class="color-option blue" data-color="blue">Blue</div>
 	 	<div class="color-option green" data-color="green">Green</div>
 	 	<div class="color-option orange" data-color="orange">Orange</div>
 	 	<div class="color-option yellow" data-color="yellow">Yellow</div>
 	 </div><!-- /.color-select -->
</div><!-- /.letter-wrapper -->

CSS

Now that we have a letter going, we can start styling it. We’re going to use ems for all our size values since eventually the font size of the blocks will change dynamically, and we want the blocks to be sized accordingly.

We need a letter wrapper with a width of 2ems, and a :before with top padding of 100% to both preserve our aspect ratio, and to position the color selection element more easily. The letter wrapper also has an active state for when the color selection tool is active. Let’s set backface-visibility: hidden to avoid repainting issues, and set some basic transition properties. For browsers that support it, we can also warn them that this element is going to have some transforms applied to it.

.letter-wrapper {
 	position: relative;
 	display: inline-block;
 	width: 2em;
 	margin: 0 0 0.5em;
 	z-index: 1;

 	cursor: pointer;

 	transition: all 0.3s;
 	backface-visibility: hidden;
 	will-change: transform;

 	&:before {
 	 	display: block;
 	 	padding-top: 100%;

 	 	content: '';
 	}

 	&.is-active {
 	 	z-index: 2;

 	 	transform: translateY(-0.75em);

 	 	transition: all 0.3s;
 	}
}

.letter-inner is going to be our block-looking element. We’re going to position it absolutely in the wrapper, give it a base background color, a box shadow, and a border radius. All styles for elements inside .letter-wrapper are nested inside .letter-wrapper in our Sass file, so we can sanely group together modifications based on the set chosen or any state changes.

.letter-inner {
 	position: absolute;
 	top: 0;
 	left: 0;
 	right: 0;
 	bottom: 0;

 	background: #feedda;
 	box-shadow: 0 -0.1em #feedda;
 	border-radius: 0.075em;
}

&.bounce .letter-inner {
	animation: bounce 0.5s;
}

This part of the block also gets a bounce animation during certain interactions. Let’s write that up now.

@keyframes bounce {
 	0% { transform: translateY(0); }
 	33% { transform: translateY(-0.25em); }
 	66% { transform: translateY(0.06em); }
 	100% { transform: translateY(0); }
}

For the blocks’ filigree, we have two SVGs that we’ll be toggling depending on which set is chosen. In an ideal world, we could use an SVG sprite and change viewBox with CSS, but at this time we can only do that with javascript. We already have a ton going on in javascript so let’s avoid adding more complexity to that file. Since we need to be able to dynamically change the SVG’s fill color, we can’t use one SVG as regular background image sprite either. So two files it is.

svg {
 	position: absolute;
 	top: 0;
 	left: 0;

 	display: none;
 	width: 100%;
 	height: 100%;
 	border-radius: 0.075em;
 	transition: all 0.3s;
}

Let’s place the actual letter in the middle of our block. We’ll be making some adjustments to individual letters based on the set/x-height later to make sure all letters are always centered, but for now let’s get the basics down.

.letter {
 	position: absolute;
 	top: 50%;
 	left: 50%;

 	text-transform: uppercase;
 	color: #fff;

 	transform: translate(-50%,-50%);
 	transition: color 0.3s;
}

Now that we have the basics of a block down, we can style the different block colors and different sets. Let’s leverage Sass and set up some color variables so we can easily loop through each one.


// block colors
$blockColors: (
	'blue': #3b83db,
	'green' : #81c81b,
	'orange': #ff900c,
	'yellow': #f5c008
);

@each $name, $color in $blockColors {
	&.#{$name} {
		svg { fill: $color; }
 		.letter { color: $color; }
 		.set-upperlower & .letter-inner { background: $color; }
	}
}

.set-abc & {
	.filigree-abc { display: block; }

	.letter {
		top: 49%;
		left: 51%;
		font-family: "Century Schoolbook", sans-serif;
	}
}

.set-upperlower & {
	.letter {
		top: 48%;

		margin-top: -1px;
		font-family: "Century Gothic", sans-serif;

		color: #feedda;
		font-size: 1.75em;
	}
}

.set-lower & {
	.filigree-lower { display: block; }

	.letter {
		margin-top: -0.125em;
		font-family: "Century Gothic", sans-serif;
		text-transform: lowercase;
	}

	&.letter-high .letter { margin-top: -0.02em; }
	&.letter-low .letter { margin-top: -0.175em; }
	&.letter-mid .letter { margin-top: -0.1em; }
	&.letter-a .letter { margin-top: -0.12em; }
}

You may have noticed some letter-based modifications on the lowercase set. Once the letters are all lowercase, while their baselines may line up, they don’t look centered in the block’s face. In our javascript, we will assign classes to the block based on the letter typed and adjust its positioning accordingly.

The last thing we need to style is the color selection tool. It’s a basic menu, with a triangle pointing toward its block and some color indicators.

.color-select {
	position: absolute;
	display: none;
	padding: 5px 0;
	margin-top: 0.4em;
	left: 50%;
	z-index: 1;

	background: $black;

	text-align: left;

	transform: translateX(-50%);
	backface-visibility: hidden;

	// triangle
	&:before {
		position: absolute;
		top: -19px;
		left: 50%;
		margin-left: -10px;

		border: 10px solid transparent;
		border-bottom-color: $black;

		content: '';
	}

	.color-option {
		padding: 5px 15px;

		white-space: nowrap;
		@include font-size(18); // font-size mixin which spits out both px and rem

		transition: background 0.3s;

		// color bullet
		&:before {
			display: inline-block;
			width: 8px;
			height: 8px;
			margin: -5px 10px 0 0;

			border-radius: 50%;

			content: '';
			vertical-align: middle;
		}

		@each $name, $color in $blockColors {
			&.#{$name}:before {  background: $color; }
		}

	 	&:hover,
		&.is-active { background: $grey-darkest; }
	}
}

Javascript

Now let’s switch over to javascript. First off, we know a lot is going to happen in this generator, so let’s set up a basic structure to our file. We can fill in the functions later. We’ll need two lock variables for controlling when events can happen, one for resize, and one for the various functions that can happen to a block. We also need functions for interactive, resize, add/edit blocks, opening/closing the color selection UI, color selecting, changing the set, and updating the cart information.

(function($){
 	var createAName = {

 	 	//------------------------------------------------------------------------
 	 	// Variables
 	 	//------------------------------------------------------------------------

 	 	resizeLock: null,
 	 	blockLock: true,

 	 	//------------------------------------------------------------------------
 	 	// Functions 
 	 	//------------------------------------------------------------------------

 	 	interactive: function() {

 	 	},

 	 	resizeEvents: function() {

 	 	},

 	 	addEditBlock: function(){

 	 	},

 	 	popColorSelection: function() {

 	 	},

 	 	colorSelection: function() {

 	 	},

 	 	changeSet: function() {

 	 	},

 	 	updateCreateAName: function() {

 	 	}
 	};

 	$(document).ready(function(){

 	 	// bind interactive events
 	 	createAName.interactive();

 	 	// resize events
 	 	$(window).resize(function(){ createAName.resizeEvents(); }).resize();
 	});

})(jQuery);

First off, let’s make some blocks. Inside our interactive function, let’s watch for typing on the input we created earlier, and trigger the addEditBlock function when someone types.

$('#create-a-name-text').keyup(function(){
	createAName.addEditBlock();
});

Before we can create the blocks, we need to strip out any non-alphabet letters, trim the length of the text if it’s beyond the max length, correct the letter case to match the set, check whether the letters typed have actually changed since the last input, and if there are any valid characters at all.

addEditBlock: function() {
	var name = $(this).val().replace(/[^a-zA-Z]/g, ''),   	//create a nice version of letters to play with
	    block_prototype = $('.letter-template'),          	//html template for a letter
	    block_wrapper = $('.letters'),                    	//the letters wrapper
	    block_set = block_wrapper.attr('data-set'),       	//the set we're using
	    block_colors = ['blue','green','orange','yellow'],	//colors

	    // group letters based on required position modification
	    block_letters_high = ['b', 'd', 'f', 'h', 'i', 'k', 'l', 't', 'B', 'D', 'F', 'H', 'I', 'K', 'L', 'T'],
	    block_letters_low = ['g', 'p', 'q', 'y', 'G', 'P', 'Q', 'Y'];
	    block_letters_mid = ['u', 'v', 'w', 'x', 'U', 'V', 'W', 'X'];

	//keep name short... 26 is plenty
	if(name.length > 26) {
		name = name.substr(0, 26);
	}

	//correct the case
	if(block_set === 'set-lower'){
		name = name.toLowerCase();
	}

	//upercase
	else {
		name = name.toUpperCase();
	}

	//exit if nothing has changed
	if(name === block_wrapper.attr('data-name')) {
		return true;
	}

	//if nothing has been typed, this is easy
	if(!name.length) {
		block_wrapper.html('');
	}

Now for the meat of this function, actually making our blocks. When blocks are initially created, colors are assigned by looping through the color array. But customers can edit their inputted text, so if they’ve already made a color selection for a letter, edit the text, and that letter is still in their edited text, we want to preserve the color selection. And though we haven’t written these functions yet, we know we want to update the cart information, and trigger the resize functions to make sure the blocks are being sized properly.

	//if there are letters, let's figure out how to draw them
	else {
		//some more variables
		var letters = name.split(''),	//each letter
		    block_new = null,        	//new block
		    color_index = -1,        	//color
		    blocks_old = [],         	//all existing blocks
		    blocks_new = [];         	//all new blocks

		//store the old blocks in an array.
		if($('.letter-wrapper', block_wrapper).length) {
			$('.letter-wrapper', block_wrapper).each(function(){
					blocks_old.push($(this));
			});
		}

		//cycle through each letter
		$.each(letters, function(k,v){
			color_index++;

			//if there is an existing letter matching this one,
			//preferentially use it (this preserves color selections)
			if(blocks_old.length > k && blocks_old[k] !== undefined && blocks_old[k].attr('data-letter') === v){
				blocks_new.push(blocks_old[k].clone());
			}
						
			//otherwise let's make a new block
			else {
				//start with the prototype
				block_new = block_prototype.clone();
				block_new.removeClass('letter-template');
				block_new.removeAttr('style');

				var xHeight = 'letter-middle';
				//check what x-height based class we should assign to it
				if($.inArray(v, block_letters_high) > -1){
					xHeight = 'letter-high';
				}

				if($.inArray(v, block_letters_low) > -1){
					xHeight = 'letter-low';
				}

				if($.inArray(v, block_letters_mid) > -1){
						xHeight = 'letter-mid';
				}

				//customize it
				$('.letter', block_new).text(v);
				$('.color-option.' + block_colors[color_index], block_new).addClass('is-active');
				block_new.attr({'data-letter': v, 'data-color': block_colors[color_index]});
				block_new.addClass(block_colors[color_index]);
				block_new.addClass(xHeight);
				block_new.addClass('letter-' + v.toLowerCase());

				//and add to our list
				blocks_new.push(block_new);
			}

			//and start over with the colors
			if(color_index + 1 === block_colors.length){
				color_index = -1;
			}
		});

		//now add each block to our set!
		block_wrapper.html('');
		$.each(blocks_new, function(k,v){
			block_wrapper.append(v);
			//if this is not the last item, add a space
			if(k + 1 < blocks_new.length){
				block_wrapper.append(' ');
			}
		});
	}//letters

	//save the name so we can skip all this work for non-changey-keys
	block_wrapper.attr('data-name', name);

	//and update the cart data
	createAName.updateCreateAName();

	//one last thing, trigger resize events so the blocks get scaled correctly
	if(createAName.resizeLock) {
		clearTimeout(createAName.resizeLock);
	}

	createAName.resizeLock = setTimeout(function(){ createAName.resizeEvents(); }, 100);
}, // end addEditBlock

Let’s tackle the resizing. We want to fit as many blocks on one line as possible while maintaining legibility. We also want to make the blocks as big as possible up to a certain point. So we need a min and a max font size, and figure out what size in that range our blocks should be given the container size and the amount of blocks. Let’s also include a little wiggle room so the blocks aren’t flush up against the container, and we need to account for the fact that the blocks are 2ems wide, so our final font size will actually be half the number we come up with.

resizeEvents: function() {
	var containerWidth = $('.letters').width(),                      // container width
	    letterAmount = $('.letters').find('.letter-wrapper').size(), // how many letters do we have
	    baseSize = containerWidth / letterAmount,                    // how wide can the blocks be to still fit in the container
	    wiggleSize = (baseSize - (baseSize * 0.15),                  // wiggle room
	    fontSize = wiggleSize / 2,                                   // blocks are 2ems wide, so font size is half size of block
	    maxSize =  96,                                               // max font size
	    minSize = 38;                                                // min font size

	if(fontSize > maxSize) {
		fontSize = maxSize;
	}

	if(fontSize < minSize) {
		fontSize = minSize;
	}

	fontSize = Math.floor(fontSize);

	$('.letters').css('font-size', fontSize + 'px');
}; // end resizeEvents and end 

Now that we’ve got the basics of block making and resizing down, we can work on the options. First up, let’s add a triggering mechanism to our interactive function for changing the block set.

$('#set-filter').on('change', function(){
	var oldSet = $('.letters').attr('data-set'),
	    newSet = $(this).val();

	createAName.changeSet(oldSet, newSet);
});

And let’s fill out our changeSet function. We want to remove the old set’s class and add the new one to all the letters, as well as update the case if necessary, and update the cart information.

changeSet: function(oldSet, newSet){
	$('.letters').removeClass(oldSet).addClass(newSet).attr('data-set', newSet);

	//update data so we're using the right case
	var block_wrapper = $('.letters');

	//lowercase
	if(newSet === 'set-lower'){
		block_wrapper.attr('data-name', block_wrapper.attr('data-name').toLowerCase());
	}
	
	//uppercase
	else {
		block_wrapper.attr('data-name', block_wrapper.attr('data-name').toUpperCase());
	}

	$('.letter-wrapper', block_wrapper).each(function(){
		//lowercase
		if(newSet === 'set-lower'){
			$('.letter', $(this)).text($('.letter', $(this)).text().toLowerCase());
			$(this).attr('data-letter', $(this).attr('data-letter').toLowerCase());
		}

		//upercase
		else {
			$('.letter', $(this)).text($('.letter', $(this)).text().toUpperCase());
			$(this).attr('data-letter', $(this).attr('data-letter').toUpperCase());
		}
	});

	//and update the cart data
	createAName.updateCreateAName();
}, // end changeSet

Moving on to color selection. The first step is to actually show the color selection menu when someone clicks on a block. We also want to close it when they click on the same block again, or on a different block. On top of that, we want to close open blocks when they click on anything that isn’t a block. To accomplish this, we’re using jQuery Outside Events to check whether a user has clicked outside an open block. This is also where our blockLock variable comes in. We just want to make sure we’re not triggering any outside events when there isn’t a block open, so we’re going to set blockLock to true initially, and set it to false when a block is open.

Let’s set up a block click event, and monitor outside events in our interactive function. As a note, anytime we’re binding an event to a block, we have to use .on() on the parent element rather than something like .click() on the element itself, since the blocks are dynamically created and .click() only binds to elements that are present when the event is first bound.

$('.letters').on('click', '.letter-inner', function(e){
	e.preventDefault();
	createAName.popColorSelection($(this));
});

// close block when you click outside of it
$('.letters').bind( 'clickoutside', function(){
	if(createAName.blockLock === false){
		$('.letter-wrapper.is-active .letter-inner').click();
	}
});

And now for our popColorSelection function.

popColorSelection: function(block){
	var parent = block.parents('.letter-wrapper');

	// if there is an active block but it is NOT the block we clicked on, close that block				
	if($('.letter-wrapper.is-active') && !parent.hasClass('is-active')){
		$('.color-select', $('.letter-wrapper.is-active')).fadeOut();
		$('.letter-wrapper.is-active').removeClass('is-active');
	}

	// toggle active class, fade toggle the menu
	parent.toggleClass('is-active');
	$('.color-select', parent).fadeToggle();

	// set our blockLock appropriately
	if(createAName.blockLock === true){
		createAName.blockLock = false;
	}

	else {
		createAName.blockLock = true;
	}
}, //end popColorSelection

Awesome. Now we can start doing all the things that need doing when someone changes the color. First up, let’s toss an event trigger in our interactive function and pass the block and menu item info to the colorSelection function.

$('.letters').on('click', '.color-option', function(e){
	var block = $(this).parents('.letter-wrapper');	
	createAName.colorSelection(block, $(this));
});

When someone selects a new color, we need to capture the old color, remove its class, add the new color class, update data-color attribute, and update the cart information. We also need to update the color menu to reflect the active color. The block also gets a little bounce animation when a new color is selected.

colorSelection: function(block, menuItem) {
	var currColor = block.attr('data-color');
	var color = menuItem.attr('data-color');

	e.preventDefault();

	block.removeClass(currColor).addClass(color).attr('data-color', color);
	block.addClass('bounce'); // add a bounce animation

	setTimeout(function(){
		block.removeClass('bounce');
	}, 500); // length of our bounce animation

	$('.color-option.is-active', block).removeClass('is-active');
	menuItem.addClass('is-active');

	//and update the cart data
	createAName.updateCreateAName();
}, //end colorSelection

The last thing we need to do is build out our function that updates the cart information. It’s pretty simple. We just run through all the blocks and gather all the info we need, and pop it back in the cart button element.

updateCreateAName: function(){
	//build the add-to-cart data
	var block_wrapper = $('.letters'),
	    block_set = block_wrapper.attr('data-set'),
	    blocks = $('.letter-wrapper', block_wrapper),
	    qty = blocks.length,
	    notes = [],
	    button = $('.add-to-cart');

	//no quantity, we're done!
	if(!qty) {
		button.attr({'data-qty': 0, 'data-note': ''});
	}

	//yes quantity
	else {
		notes.push($('#set-filter option[value="' + block_set + '"]').text());
		blocks.each(function(){
			notes.push('[' + $(this).attr('data-letter') + '] ' + $(this).attr('data-color'));
		});

		button.attr({'data-qty': qty, 'data-note': notes.join(';')});
	}
}, // end updateCreateAName

That’s it. Be sure to check out the Create-a-Name generator on the Uncle Goose website, along with all their other awesome block sets!

Posted By
Tiffany Stoik

How-To: 8-Bit Hovers

For The Yetee’s redesign, we wanted to come up with hover styles that were unique and kept with the brand’s playful feel.

We played around with a few different options, but ultimately settled on these two 8-bit inspired hover effects.

See the Pen 8-bit Hovers by Tiffany Stoik (@tstoik) on CodePen.

Let’s walk through how they’re put together.

8-Bit Buttons

The idea behind these buttons is a pixelated side-swipe effect. When you hover, 5 bars stacked vertically slide in staggered fashion from the left. Each bar also has a square at the end of it, that flash between darker/lighter tones.

Note: In the code below, my colors have been saved as variables, and I’m only showing the styles for the desktop media query for brevity’s sake. All media query styles can be found on CodePen.

First, let’s set up our elements. These styles could work on regular anchors or buttons, but let’s use anchors for now.

<a href="#" class="btn red" title="Red">Red</a>
<a href="#" class="btn green" title="Green">Green</a>
<a href="#" class="btn blue" title="Blue">Blue</a>

Next, let’s get some basic styles for this button. Be sure to add z-index: 1 so that we can set a negative z-index on our hover panel. This will make sure it sits behind the text instead of in front of it, and that it’s not hidden behind the button’s background color.

.btn {
	position: relative;
	display: inline-block;
	margin: 0 10px;
	padding: 18px 30px;
	z-index: 1;

	border: 0; // in case this is a button element
	outline: 0; // in case this is a button element
	cursor: pointer; // in case this is a button element

	@include font-size(12);
	color: $white;
	text-align: center;
	line-height: normal;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	font-weight: bold;

	// pseudo-border, so that the hover panel will cover it up on hover
	&:after {
		position: absolute;
		right: 0;
		bottom: 0;
		left: 0;
		height: 6px;
		z-index: -2;

		content: '';
	}

	&.red { background: $red; }
	&.green { background: $green; }
	&.blue { background: $blue; }
}

Now since we need five bars + five squares for the hover, we need more elements than pseudo-elements could provide. My strategy was to use a wrapper div for the whole hover, and a span for each bar, using pseudo-elements for the squares. I could have accomplished the desired effect using fewer elements, but using one wrapper and one element for each bar makes the CSS much more straight-forward.

We could add these extra elements directly in the HTML, but if you have a lot of buttons on one page, or even just a lot of other code on the page, it can get messy quickly. I opted to add the element to the DOM with javascript (jQuery to be precise) on load. This is optional and you could definitely keep the extra elements in the HTML.

// add extra elements to buttons for hover, keeps html cleaner
$('.btn').prepend('<div class="hover"><span></span><span></span><span></span><span></span><span></span></div>');

Now here’s where the complicated stuff starts to happen. Let’s style the hover panel and its children. We just want the hover panel to be positioned absolutely in the whole space of the button. Spans are going to be the vertical bars, with each span’s :after being used as the square.

Each vertical bar starts off with a different offset from the edge of the button, and has a different transition duration. To capture more of that 8-bit flavor, we’re using steps in the transition to make it less smooth.

.btn .hover {
	position: absolute;
	top: 0;
	right: 0;
	bottom: 0;
	left: 0;
	z-index: -1;
	overflow: hidden;

	span {
		position: relative;
		display: block;
		left: -15px;
		height: 10px;
		width: 0;

		content: '';
	}

	span:after {
		position: absolute;
		display: block;
		right: -10px;
		width: 10px;
		height: 10px;

		background: $white; // light-toned squares

		content: '';
	}

	span:nth-child(odd) {
		&:after { background: rgba($black, 0.35); } // dark-toned squares
	}

	span:first-child {
		left: -75px; // staggered offset
		transition: all 0.3s steps(8);
	}

	span:nth-child(2) {
		left: -45px; // staggered offset
		transition: all 0.325s steps(8);
	}

	span:nth-child(3) {
		left: -55px; // staggered offset
		transition: all 0.35s steps(8);
	}

	span:nth-child(4) {
		transition: all 0.4s steps(8);
	}

	span:nth-child(5) {
		left: -25px; // staggered offset
		transition: all 0.375s steps(8);
	}
}

Alright. Now for the actual magic. What happens when you hover? Actually not really that much. First, the spans transition to full width + offset + 1px — on some retina devices I found that without that extra pixel you could see the edge of the square. The transitions also have to be adjusted to mirror the unhover/blur transitions. So the shortest transition duration above now gets the longest transition duration, etc. You should technically be able to just adjust the transition-duration property but when I tried this, there were some browser compatibility issues, so I’m just repeating the whole transition declaration.

The only other thing going on is the little squares flashing color. We’ve got one animation, running in alternate directions for alternate squares (without prefixes for sanity). They run for the same length of time but start at different offsets.

.btn:hover .hover {
	span:first-child {
		width: calc(100% + 76px);
		transition: all 0.4s steps(8);

		&:after { animation: whiteBlack 0.3s 0s 1 forwards; }
	}

	span:nth-child(2) {
		width: calc(100% + 46px);
		transition: all 0.375s steps(8);

		&:after { animation: whiteBlack 0.3s 0.06s 1 reverse backwards; }
	}

	span:nth-child(3) {
		width: calc(100% + 56px);
		transition: all 0.35s steps(8);

		&:after { animation: whiteBlack 0.3s 0.05s 1 forwards; }
	}

	span:nth-child(4) {
		width: calc(100% + 16px);
		transition: all 0.3s steps(8);

		&:after { animation: whiteBlack 0.3s 0s 1 reverse backwards; }
	}

	span:nth-child(5) {
		width: calc(100% + 26px);
		transition: all 0.325s steps(8);

		&:after { animation: whiteBlack 0.3s 0.07s 1 forwards; }
	}
}

@keyframes whiteBlack {
	0%,
	24% { background:  $white; }

	25%,
	49% { background: rgba($black, 0.35); }

	50%,
	74% { background: $white; }

	75%,
	100% { background: rgba($black, 0.35); }
}

And that’s it for the buttons!

8-bit Social Icons

Let’s start off with some anchors. I’m using a icon font for the icons, so we just need to use the appropriate classes.

<div class="social-buttons">
	<a href="#" class="social-btn entypo-tumblr" title="Tumblr" target="_blank"></a>
	<a href="#" class="social-btn entypo-twitter" title="Twitter" target="_blank"></a>
	<a href="#" class="social-btn entypo-facebook" title="Facebook" target="_blank"></a>
	<a href="#" class="social-btn entypo-instagrem" title="Instagram" target="_blank"></a>
</div>

Basic styles. We just want a big ol’ circle and some pretty colors for the icons.

.social-btn {
	position: relative;
	z-index: 1;
	display: inline-block;
	margin: 30px 15px;
	width: 96px;
	height: 96px;

	background: $white;
	border-radius: 50%;

	line-height: 96px;
	@include font-size(45);

	&:before { line-height: inherit; } // make sure our icons are in the right place

	&.entypo-tumblr { color: $navy-darker; }
	&.entypo-twitter { color: $blue-light; }
	&.entypo-facebook { color: $blue-darker; }
	&.entypo-instagrem { color: $pink; }
}

Like with the buttons, I need more elements than I have. To get the 8-bit effect, we’re essentially going to “trace” the circle with squares and rectangles to create the pixelated look we want. For each “side”, we want three elements: one rectangle to trace the main edge and two squares to “curve” down. To keep everything neat, I’m going to use jQuery to generate 4 spans, one for each side.

$('.social-btn').prepend('<span></span><span></span><span></span><span></span>');

Now let’s style them! They’re going to start out with no opacity and scaled down to zero. When we hover, they will scale back to normal.

We’re just making this pixelated outline big enough that all the pieces fit together and outline the circle nicely. I went for a diameter of 108, 12 pixels bigger than the 96px circle. The number looked good and was divisible by 2. I knew I wanted 10px corner pieces, so from there I just had to do the math to figure out what size the long pieces should be. Each rectangle is going to be 48px on its long side and 10px on its short side. The squares are actually 10px by 16px or vice versa — wider for the top/bottom sides, taller for the left/right sides. They’re still visually 10px, we just need to make up that extra 12px to cover up any gaps where the sides meet the circle.

In testing, they were a little jittery and had some painting issues. Tossing both backface-visibility: hidden; and a perspective on them helped. The other thing that helped was transitioning only the transform on hover, and transitioning both transform and opacity on blur.

.social-btn { ...
	span {
		position: absolute;
		display: block;

		background: $white;
		opacity: 0;

		transform: scale(0);
		transition: transform 0.3s, opacity 0s 0.3s;

		backface-visibility: hidden;
		perspective: 1000;

		&:before,
		&:after {
			position: absolute;
			display: block;
			width: 10px;
			height: 10px;

			background: $white;

			content: '';
		}
	}

	span:first-child { top: -6px; } // half the diameter offset
	span:nth-child(2) { right: -6px; }
	span:nth-child(3) { bottom: -6px; }
	span:last-child { left: -6px; }

	// top and bottom pieces
	span:first-child,
	span:nth-child(3) {
		left: 24px;
		width: 48px;
		height: 10px;

		&:before,
		&:after { width: 16px; } // original width + half diameter offset
	}

	// left and right pieces
	span:nth-child(2),
	span:last-child {
		top: 24px;
		width: 10px;
		height: 48px;

		&:before,
		&:after { height: 16px; } // original height + half diameter offset
	}
}

Now let’s position the squares. Each square is a pseudo element on the rectangular span. We’re going to be moving by 10px in whichever direction is appropriate (eg: for the top piece, 10px down and 10px to the left/right).


.social-btn { ...
	span:nth-child(2):before,
	span:last-child:before,
	span:nth-child(3):before,
	span:nth-child(3):after {
		top: -10px;
	}

	span:first-child:after,
	span:nth-child(3):after,
	span:last-child:before,
	span:last-child:after {
		right: -10px;
	}

	span:nth-child(2):after,
	span:last-child:after,
	span:first-child:before,
	span:first-child:after {
		bottom: -10px;
	}

	span:first-child:before,
	span:nth-child(3):before,
	span:nth-child(2):before,
	span:nth-child(2):after {
		left: -10px;
	}
}

After all that, the hover is pretty simple. Grow it!

.social-btn:hover {
	span {
		opacity: 1;
		transform: scale(1);
		transition: transform 0.3s;
	}
}

And that’s how you make 8-bit hovers! Be sure to check out The Yetee, where you can see these hovers in action, and lots of other awesome stuff.

Posted By
Tiffany Stoik