With the current design of this site I wanted to have images in the content that stretch out beyond the container. I’ve seen this pattern used quite a bit recently and I think it gives a nice little visual “pop”. Implementation wise this is a pretty easy problem to solve by using negative margins. I decided I would use this as an opportunity to put together a little centerImg jQuery plugin and document my steps along the way.

Here is a picture of the result we are going for:

To get started with the tutorial first we have to get our directory setup. You can skip this if you desire but since I’m sharing I want to be as thorough as possible. For this plugin my requirements are simple. I want to run jshint, uglify to create a min file, and have these both automated via gulp.

First create a package.json with an empty json object in it. By creating the file it will allow npm –save-dev to add dependencies automatically. Once that is saved open terminal and cd into your directory. Here are the commands to get us rolling with gulp:

npm install --save-dev gulp gulp-jshint gulp-rename gulp-uglify

That command will take a few minutes to run. As it’s running switch back to your editor and create a new file named “gulpfile.js” and start getting it setup.

Gulp – jshint and uglify

At the top you need to import all your packages that are installing from NPM:

var gulp = require('gulp'), jshint = require('gulp-jshint') rename = require('gulp-rename') uglify = require('gulp-uglify');

Next we create a “compress” task:

gulp.task('compress', function() { gulp.src('src/*.js') .pipe(uglify()) .pipe(rename({suffix: '.min'})) .pipe(gulp.dest('dist')) });

What this will do is pull in any .js files in the “src/” directory, run uglify on them, rename them with a .min extension, and finally save it in the “dist/” directory.

Our second task will be running jshint to hopefully catch any errors we may inadvertently put in our code. Here is what it looks like:

gulp.task('lint', function() { return gulp.src(['src/*.js']) .pipe(jshint()) .pipe(jshint.reporter('default')); });

As you can hopefully see it performs much the same way as our compress task. We tell it the pattern to find the js files in “src”, pipe those to jshint(), and use the default reporter if anything goes wrong.

Finally we add a default task so we can just run “gulp” and have all this run automatically.

gulp.task('default', function() { gulp.start('jslint', 'compress'); });

Here is a link to the completed gulpfile so you can see the full file.

jQuery Plugin Code

Now with gulp setup its time to start on our actual plugin code. If you’ve never written a jQuery plugin before then I recommend reading through the Learn jQuery Plugins documentation.

Before we actually start writing any JavaScript I like to think through the implementation from the end users perspective. If I was using this plugin, how would I want to instantiate it? I came up with this:

$('img.popout').centerImg({ container: $(".content") });

If I’m being honest I would prefer not to pass in the container element but the plugin is going to need something to calculate the width from. For now I can live with it but it’s something I may consider refactoring later.

Now I put the very minimal plugin code in ./src/centerImg.js:

(function($) { $.fn.centerImg = function( options ) { //.. }; }(jQuery));

With the basics set you can hop back to your terminal and run “gulp” which should lint and compress the file without errors.

Finally its time to start writing some code. Go back to our plugin file and under the $.fn.centerImg is where we will start writing. The first section we need is some default settings and to loop all the matching selectors. Remember from earlier we passed in a container object so that will be first:

var settings = $.extend({ container : $("body") }, options); var containerWidth = settings.container.innerWidth(); return this.each( function() { //.. });

What I’m doing here is setting a default container of the body and allowing the end user to over ride it with an element of their choice. Then setting a local variable of containerWidth which we will use for calculating the negative margin.

Inside the “this.each” is where the processing needs to happen. This also is a part that tripped me up. You can’t just call $(this).width() on the image as that only returns the parsed width. A lot of times for responsive design the styles would have something like:

img { height: auto; width: auto; }

So instead of giving the true width we would only get the container width. One workaround I found was using JavaScript Image() object. The code looks like this:

// Get a true image width no matter what styles are applied var image = new Image(); image.src = $(this).attr("src"); var imgWidth = image.naturalWidth; // IE9+

That gives us the true image width and now we can begin our calculations. Its pretty simple and it just takes division and subtraction:

marginLeft = (imgWidth / 2) - (containerWidth / 2);

We take the true image width divide by 2, the container width divide by 2, and subtract each. That leaves us a number to then set the negative margin:

return $(this).attr("style", "margin-left: -" + marginLeft + "px;");

Now at this point I loaded it up on my local site and started playing around with different images to make sure it works and see if any edge cases are found. I actually found a few problems. The first is images smaller than the container, and the second is when an image is huge. I only wanted it to be a little larger than the container but others may want it to go as big as possible. This should be configurable.

If an image is smaller is an easy fix. Lets just add a simple check after we set the imgWidth:

if (imgWidth <= containerWidth) { return this; }

No reason to do anything with it really. The issue of setting a max width is a little trickier. We just need to go back to our settings and add one for maxWidth, defaulting to the body width:

var settings = $.extend({ container : $("body"), maxWidth: $("body").innerWidth() }, options);

Then before our calculations add in a new if check:

if (imgWidth > settings.maxWidth) { $(this).attr("width", "" + settings.maxWidth + "px"); imgWidth = settings.maxWidth; }

This just sets a style attribute on the image tag and at this point we are pretty much done. Go back to terminal run gulp to compress it and implement it into the site.

Here is the completed plugin file so it’s easier to follow and see how it all turned out.

Wrap Up

After writing this plugin and this post I realized that I do not like the name centerImg. To me centerImg implies it’s going to center an image no matter what. I think a better name might be popImage or billboard.

There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton

If this was something more I would sit on it for a few days and really give the name a lot of thought, but in this case I’m rolling with it. Also is a plugin even needed? I’m sure this can be done with pure CSS, heck I’ve seen an animated Breaking Bad logo. If that’s possible I know this is. 🙂