f w h

Stop Setting Breakpoints

Ever since Ethan Marcotte’s amazing article on ALA way back in 2010, web developers have been leveraging media queries to ensure frontend designs are usable across devices. In general, that means setting breakpoints with new styles for each device size. This new idea lurched web interface design forward, and the idea of responsive web design has become not only ubitquitous, but standard. Most web design/development courses include the concept as part of their curriculum.

What web developer hasn’t played with the width of their browser window, resizing until it breaks, and then setting a breakpoint at that width. Lather, rinse, repeat until the design works cross-browser. I am a firm believer in the concept that if it works, it’s not wrong. However, I think there’s a better way.

Viewport Units

Support for viewport units in CSS is rather high. The vh and vw units are almost universally usable. I would say for almost all projects, they’re safe to use. Support for vmax and vmin isn’t quite as good, and that’s the only reason I even discuss continued use of media queries at all in this article. Put another way, once vmin and vmax support widens, media queries can become almost unnecessary.

The Only Media Query You’ll Ever Need

I present to you the only media query you’ll ever need: orientation. The team up of viewport units and the orientation query can’t solve every design problem, but it can come pretty close.

Consider this scenario: You are working on a design in-browser. It has three blocks arranged horizontally. In landscape, which is the orientation most desktop users are going to be using, The blocks fit well, and their content is easily readable. However, below 600px width, the content overflows the box, causing the last box to drop to the next line. Normally in a situation like this, because the font-size is set using em, rem or some other either relative or static sizing, you would likely set a breakpoint at 600px width, then adjust the font size.

See the Pen wpjdGN by Ronald Roe (@ronaldroe) on CodePen.

This is where viewport units come in. Instead of using the relative or static units like normal, set the font-size based on the width of the viewport. For most paragraph text, I find 2 – 3vw works well, but use whatever works for your design.

With the vw units set, the content will no longer overflow the box as the size shrinks. In most cases, the breakpoint becomes unnecessary. Next, in our imaginary scenario, we try the design in portrait orientation on a phone and find that the boxes are small and the text is too small to read. What happened? Since the text size is based on the viewport’s width, and the width is much smaller than the height, the text looks tiny.

That’s where our orientation media query comes in. Setting @media (orientation: portrait) will trigger when the height of the viewport is greater than the width. Now, inside the query, reset the font size to use vh units. In general, using the same number as the vw value will maintain a similar if not consistent look. Additionally, most scenarios with floated boxes will look better if the float is removed (set to none) in portrait.

See the Pen baMWRP by Ronald Roe (@ronaldroe) on CodePen.

There will be cases where breakpoints are still necessary. However, this method should reduce the need considerably. I’ve used this method in all of my current and some of my recent past projects, and am having a lot of success with it. For my current project, a frontend for Shape Fitness, You will not find a single breakpoint in the CSS.

Let me know in the comments and on Twitter what you think.

Update Node via Command Line Without a Version Manager

Searching for how to do this via command line comes up with a whole lot of Stack Overflow posts on how to use either n or NVM to control your Node version. It isn’t a bad idea, but sometimes, like when I’m using an online IDE like Cloud 9, I know I have no need whatsoever to use a version manager.

So, I just tried npm i -g node, and it did the trick. Seems simple and like something any Node dev should know, but I never have done it, and searching Google didn’t turn up anything that didn’t use a version manager.

So, there you go. Simple as that.

Viewport Units on SVG

A quick note, learned from experience: viewport units (vw, vh, vmin, vmax) do not work as height or width attributes in Firefox. I tested the latest (as of this post’s pub date) versions of: Chrome, Firefox, Edge and IE11. It seems to work perfectly in all of those except Firefox.

What do I mean? Take a look:


<svg height="10vh" width="10vw">
	<!-- SVG innards -->
</svg>

This works with no issue in most browsers, but will be ignored in Firefox. Why? I’m guessing it’s Firefox sticking closely to the spec, as Mozilla doesn’t list them as an available option in their MDN document on SVG sizes, and neither does the W3C spec for the length data-type on SVGs.

Fret not, however, because I have a fix for it.

Viewport units are simply a percentage of the viewport’s, or as the CSS spec states, ‘initial containing block’ size. Thus, if your viewport is 1000px wide, 1vw is equivalent to 10px. The only problem is that it will need to adjust automatically when the viewport changes.

The script below sets the viewport units aside in a data-* attribute, then uses them to calculate a pixel value for them:


// My viewport script from a previous post
function viewport(){

	let w = window.innerWidth;
	let h = window.innerHeight;
	
	let orientation = w > h ? 'landscape' : 'portrait';
	
	let output = {
		w: w,
		h: h,
		orientation: orientation
	};

	return output;

}

// Converts the viewport units to a pixel-based length
function viewport2px(size){

	let int = parseInt(size, 10); // Grabs the size and extracts only the number
	let output;
	
	size.includes('h') ? output = ((int / 100) * viewport().h) + 'px' :  output = ((int / 100) * viewport().w) + 'px';
	
	return output;

}

// Sets the new units according to their current size
function sizeSVGs(svgsIn){

	// If a NodeList object is not provided, just select all of them
	let svgs = svgsIn || document.querySelectorAll('svg');
	
	Array.from(svgs).forEach(function(svg){
		// Check the height for viewport units (doesn't check for vmin/vmax - easy enough to add),
		// Sets those units aside for later use,
		// Sets the new px size
		if(svg.getAttribute('height').includes('vw') || svg.getAttribute('height').includes('vh')){
			svg.setAttribute('data-height', svg.getAttribute('height'));
			svg.setAttribute('height', viewport2px(svg.getAttribute('height')));
		}
		// Does the same thing for width
		if(svg.getAttribute('width').includes('vw') || svg.getAttribute('width').includes('vh')){
			svg.setAttribute('data-width', svg.getAttribute('width'));
			svg.setAttribute('width', viewport2px(svg.getAttribute('width')));
		}
	});

}

// Fire on load
window.addEventListener('load', function(){

	sizeSVGs();

});

But what about the resize? Well, remember how we set aside the initial values in a data-*attribute? We just need to grab that value again and recalculate any time a resize event fires.


window.addEventListener('resize', function(){
	Array.from(svgs).forEach(function(svg){
		if(svg.hasAttribute('data-height')) svg.setAttribute('height', vp2px(svg.getAttribute('data-height')));
		if(svg.getAttribute('data-width')) svg.setAttribute('width', vp2px(svg.getAttribute('data-width')));
	});
});

Caveat: This script uses the Array.from()method to convert the SVG NodeList into an array for iteration. No version of IE supports this method. If IE support is required, MDN has a great drop-in polyfill.

WordPress Development Environment Scripts

It is useful to have certain items or scripts available to you while developing that you may not want present in production. Forgetting to remove these items before you go to production can be dangerous.

When developing with WordPress, a short script in your functions.php can add these tools when you’re developing locally, but hide them when live.

Create your scripts in a separate file in the js directory of your theme. I called mine dev.js. Then, add the following to your functions.php file:


function add_dev_class( $classes ) {
 
    $classes[] = 'dev';
     
    return $classes;
     
}

function enqueue_dev(){

    wp_register_script( 'Dev', get_stylesheet_directory_uri() . '/js/dev.js', [ 'jquery' ], NULL, true );
    wp_enqueue_script( 'Dev' );

}

// Here comes the magic
if( $_SERVER[ 'SERVER_NAME' ] == "localhost" ){

    add_filter( 'body_class', 'add_dev_class' );
    add_action( 'wp_enqueue_scripts', 'enqueue_dev' );

}

The first function will add a class called “dev” to the body tag, so you can hook it with JavaScript or add development-centric CSS styles.

The second function will enqueque the dev.js script file.

Finally, the conditional at the end will check what the current base address is and if it matches, will execute the functions. I’ve used localhost here, but you can use whatever your development domain is.

I use this to load a variation of my viewport size bookmarklet that sits at the bottom of the window with a plethora of information to help inform my decisions when designing for responsiveness.

Mark Your Spot on a Page – Bookmarklet

Recently, I was trying to work my way through an HTML-based online book. It became a pain to find my spot again when I had to close the browser or reboot the computer for various reasons. So, I created a bookmarklet to help me find my spot again when I return to the page. The bookmarklet adds a small box in the upper right corner of the window with buttons to add or clear bookmarks. When you click the ADD button, you’ll be prompted to name the bookmark, which will then be added to the list. Bookmarks are stored in local storage, so they will persist from session to session. You’ll just have to click the bookmarklet again to reload them.

Go HERE to get the bookmarklet. Full code is below:


function bm_go(){

  var bm_block;

  bm_block = document.createElement('div');
  bm_block.classList.add('bm_class');
    bm_block.style.width = '100px';
    bm_block.style.minHeight = '33px';
    bm_block.style.position = 'fixed';
    bm_block.style.padding = '5px';
    bm_block.style.top = '10px';
    bm_block.style.right = '10px';
    bm_block.style.backgroundColor = 'white';
    bm_block.style.fontFamily = 'Arial, sans-serif';
    bm_block.style.color = 'black';
    bm_block.style.borderRadius = '5px';
    bm_block.style.border = '1px solid #999';
    bm_block.style.zIndex = '10000000';

  var bm_add_button = document.createElement('div');
  	bm_add_button.classList.add('bm_add_button');
    bm_add_button.style.display = 'inline-block';
    bm_add_button.style.height = '33px';
    bm_add_button.style.width = '45%';
    bm_add_button.style.border = '1px solid #999';
    bm_add_button.style.fontSize = '15px';
    bm_add_button.style.textAlign = 'center';
    bm_add_button.style.lineHeight = '33px';
    bm_add_button.style.cursor = 'pointer';
    bm_add_button.style.boxShadow = '2px 2px #666';
    bm_add_button.innerText = 'ADD';
  bm_block.appendChild(bm_add_button);

  var bm_clr_button = document.createElement('div');
  	bm_clr_button.classList.add('bm_clr_button');
    bm_clr_button.style.display = 'inline-block';
    bm_clr_button.style.height = '33px';
    bm_clr_button.style.width = '45%';
    bm_clr_button.style.border = '1px solid #999';
    bm_clr_button.style.fontSize = '15px';
    bm_clr_button.style.textAlign = 'center';
    bm_clr_button.style.lineHeight = '33px';
    bm_clr_button.style.cursor = 'pointer';
    bm_clr_button.style.boxShadow = '2px 2px #666';
    bm_clr_button.style.marginLeft = '5%';
    bm_clr_button.innerText = 'CLR';
  bm_block.appendChild(bm_clr_button);

  var bm_message = document.createElement('div');
  	bm_message.classList.add('bm_message');
    bm_message.style.marginTop = '5px';
    bm_message.style.marginBottom = '5px';
    bm_message.style.fontSize = '10px';
    bm_message.style.textAlign = 'center';
    bm_message.innerText = 'Click "ADD" to set bookmark at current scroll position.';
  bm_block.appendChild(bm_message);
    
  	document.body.appendChild(bm_block);
  
  var bm_list_init = JSON.parse(localStorage.getItem('bm_list')) || [];
   
  function bm_pop_list(list){
  	
    var rm_bm = document.querySelector('.bm_bm_list');
    if (rm_bm) rm_bm.parentNode.removeChild(rm_bm);
    
    var bm_bm_list = document.createElement('ul');
    bm_bm_list.classList.add('bm_bm_list');
    bm_bm_list.style.paddingLeft = '0';
    
    for (var i = 0; i < list.length; i++){

      var bm_list_item = document.createElement('li');
      bm_list_item.classList.add('bm_list_item');
      bm_list_item.innerText = list[i].name;
      bm_list_item.setAttribute('data-x', list[i].x);
      bm_list_item.setAttribute('data-y', list[i].y);
      bm_list_item.style.marginLeft = '0';
      bm_list_item.style.listStyleType = 'none';
      bm_list_item.style.cursor = 'pointer';

      bm_list_item.addEventListener('click', function(){

      	bm_go_to_location(bm_list_item);

      });

      bm_bm_list.appendChild(bm_list_item);

    }
    
    bm_block.appendChild(bm_bm_list);

  }
  
  bm_pop_list(bm_list_init);
  
  function bm_go_to_location(item){

  	window.scrollTo(item.getAttribute('data-x'), item.getAttribute('data-y'));

  }
  
  function bm_add_item(){

  	var bm_get_name = prompt('Enter Bookmark Name', 'bookmark'),
      	bm_get_x = window.pageXOffset,
        bm_get_y = window.pageYOffset;
    var bm_list = JSON.parse(localStorage.getItem('bm_list')) || [];

    bm_list.push({
    	name: bm_get_name,
      x: bm_get_x,
      y: bm_get_y
    });

    localStorage.setItem('bm_list', JSON.stringify(bm_list));
    bm_pop_list(bm_list);

  }
  
  function bm_clear(){

  	localStorage.removeItem('bm_list');
    bm_pop_list([]);

  }
  
  bm_add_button.addEventListener('click', bm_add_item);

  bm_clr_button.addEventListener('click', bm_clear);
  
}

var bm_ = new bm_go();
Next Page »