Javascript News
Build Awesome Apps with CSS3 Animations
Today’s HTML5 applications can provide awesome experiences thanks to the new CSS3 specifications. One of them is CSS3 Animations. It can help you building rich animations on HTML elements. This can provide interesting feedbacks to the users and enables fast & fluid UIs. As those new animations are most of the time hardware accelerated by the GPU, they definitely raise the quality bar of the new generation of HTML5 applications.
According to the “CSS Animation Module Level 3” specification on the W3C site, CSS3 Animations introduces defined animations, which specify the values that CSS properties will take over a given time interval. This specification is an extension to CSS Transitions.
As CSS3 Animation is an extension to CSS3 Transitions, you should first read the article of my colleague David Catuhe on Transitions here: Introduction to CSS3 Transitions.
We’ll see in this article an interesting demo highlighting the potential of CSS3 animations, how to build simple animations & how to handle fallback in JavaScript:
Let’s first start by quickly demonstrating what CSS3 Animations are. Here is a sample animation of a Star Wars AT-AT which uses CSS3 Animations to animate parts of the transport (and which will fall back to JavaScript if your browser doesn’t support CSS3 Animations):
You can test this sample also in a separate window here: http://david.blob.core.windows.net/html5/css3atat/index.htm
Note: this sample has been tested successfully with native animations under IE10 PP3/PP4, Chrome 15, Firefox 8 & iPad 2 and with JS fallback under IE9 desktop & mobile (Windows Phone). For an unknown reason, it behaves in weird way under Opera 11.50 but works fine with the 11.60. Moreover, our lovely blogging platform is most of the time forcing the IE9 rendering engine via a meta tag. To force it back to the IE10 standards mode, press the F12 key and change the value of “Document Mode” back to IE10. Otherwise, view the demo in a separate window.
This sample is based on the awesome work done by Anthony Calzadilla. You can check other incredible demos on his website here: http://www.anthonycalzadilla.com . I’m a huge fan of the I twitty the fool sample using SVG & CSS3 Animation for instance.
CSS3 AnimationsIntroductionLet’s first review on what you can play to build the animations. CSS3 Animations works basically on the same values as CSS3 Transition.
Here they are:
- color: interpolated via red, green, blue and alpha components (treating each as a number, see below)
- length: interpolated as real numbers.
- percentage: interpolated as real numbers.
- integer: interpolated via discrete steps (whole numbers). The interpolation happens in real number space and is converted to an integer using floor().
- number: interpolated as real (floating point) numbers.
- transform list: see CSS Transforms specification: http://www.w3.org/TR/css3-2d-transforms/
- rectangle: interpolated via the x, y, width and height components (treating each as a number).
- visibility: interpolated via a discrete step. The interpolation happens in real number space between 0 and 1, where 1 is “visible” and all other values are “hidden”.
- shadow: interpolated via the color, x, y and blur components (treating them as color and numbers where appropriate). In the case where there are lists of shadows, the shorter list is padded at the end with shadows whose color is transparent and all lengths (x, y, blur) are 0.
- gradient: interpolated via the positions and colors of each stop. They must have the same type (radial or linear) and same number of stops in order to be animated.
- paint server (SVG): interpolation is only supported between: gradient to gradient and color to color. They then work as above.
- space-separated list of above: If the lists have the same number of items, each item in the list is interpolated using the rules above. Otherwise, no interpolation.
- a shorthand property: If all the parts of a shorthand can be animated, then interpolation is performed as if each property was individually specified.
And the following properties must be supported for animations:
- background-color (color)
- background-image (only gradients)
- background-position (percentage and length)
- border-bottom-color (color)
- border-bottom-width (length)
- border-color (color)
- border-left-color (color)
- border-left-width (length)
- border-right-color (color)
- border-right-width (length)
- border-spacing (length)
- border-top-color (color)
- border-top-width (length)
- border-width (length)
- bottom (length and percentage)
- color (color)
- crop (rectangle)
- font-size (length and percentage)
- font-weight (number)
- grid-* (various)
- height (length and percentage)
- left (length and percentage)
- letter-spacing (length)
- line-height (number, length and percentage)
- margin-bottom (length)
- margin-left (length)
- margin-right (length)
- margin-top (length)
- max-height (length and percentage)
- max-width (length and percentage)
- min-height (length and percentage)
- min-width (length and percentage)
- opacity (number)
- outline-color (color)
- outline-offset (integer)
- outline-width (length)
- padding-bottom (length)
- padding-left (length)
- padding-right (length)
- padding-top (length)
- right (length and percentage)
- text-indent (length and percentage)
- text-shadow (shadow)
- top (length and percentage)
- vertical-align (keywords, length and percentage)
- visibility (visibility)
- width (length and percentage)
- word-spacing (length and percentage)
- z-index (integer)
- zoom (number)
The properties of SVG objects are animatable when they are defined as animatable:true in the SVG specification: http://www.w3.org/TR/SVG/struct.html. But at the time where this article is written, I didn’t manage to combine CSS3 Animation directly on SVG elements in any of the latest browsers versions. Today’s samples on the web are then doing a little trick: they are embedding SVG resources into different DIV animated by CSS3 like the I twitty the fool sample.
DeclarationsTo declare an animation in a CSS file, here is the kind of generic code you’ll need to write:
@keyframes name_of_the_animation { from { property_to_animate: initial_value; } 50% { property_to_animate: intermediate_value; } to { property_to_animate: final_value; } }Which could also be written like that:
@keyframes name_of_the_animation { 0% { property_to_animate: initial_value; } 50% { property_to_animate: intermediate_value; } 100% { property_to_animate: final_value; } }This animation definition declares 3 steps 0, 50 & 100%. You should at least set a from (or 0%) and a to (or 100%) steps to build a correct animation (minimum 2 steps thus). Once done, you may add as many keyframes as you’d like between 0 and 100% to handle precisely the various steps of your animations.
Once the definition is declared, you can affect it to an element using the classical CSS3 selectors and you’ll need also to configure the animation options. Here the kind of generic blocks you’ll see:
#id_of_the_html_element { animation-name: name_of_the_animation; animation-duration: number_of_seconds s; animation-iteration-count: number | infinite; }To better understand, let’s review a real sample. First of all, as the CSS3 Animations specification is still in a draft stage, you’ll need to use the appropriate vendor prefix. Let’s use IE10 as a sample with the –ms prefix then. Let’s now see how the head of our AT-AT is moving.
Here’s the animation declaration:
@-ms-keyframes rotate-skull { 0% { -ms-transform: rotate(0deg) } 25% { -ms-transform: rotate(15deg) } 50% { -ms-transform: rotate(-5deg) } 55% { -ms-transform: rotate(0deg) } 75% { -ms-transform: rotate(-10deg) } 100% { -ms-transform: rotate(0deg) } }We’ve got 6 steps (0, 25, 50, 55, 75 & 100%) working on the CSS3 2D transform attributes by changing the value of the rotation.
The animation is then applied via this CSS rule:
#skull { -ms-animation-name: rotate-skull; -ms-animation-duration: 7s; -ms-animation-iteration-count: infinite; }We’re targeting the <div> element having the “id=skull” and we’re applying the animation named “rotate-skull” on it. The animation will have to be completed in 7s and be played an infinite number of times.
Here is the living result if your browser supports CSS3 Animations:
We could have written this rule in a shorter manner using the animation shorthand property:
#skull { -ms-animation: rotate-skull 7s infinite; }The animations will be triggered as soon as a matching rule is applied. You can then play or stop animations simply via JavaScript or via CSS3 to play with the classes affected to a tag.
Non-linear animationsThe “animation-timing-function” property can be used if you want non-linear animations. You can even mix the type of timing functions during each keyframe.
Basically, CSS3 animations will use cubic bezier curve to smooth the animation by computing different speed over its duration.
The following functions are supported:
- linear: Constant speed
- cubic-bezier: Speed will be computed according to a cubic bezier curve define by two control points : P0 and P1 (so you will have to define 4 values here : P0x, P0y and P1x, P1y.
- ease: Speed will be computed with cubic-bezier(0.25, 0.1, 0.25, 1)
- ease-in: Speed will be computed with cubic-bezier(0.42, 0, 1, 1)
- ease-inout: Speed will be computed with cubic-bezier(0.42, 0, 0.58, 1)
- ease-out: Speed will be computed with cubic-bezier(0, 0, 0.58, 1)
Here is a simulation tool written by David Catuhe that uses pure JavaScript to show the impact of each timing function:
Note: this tool uses in-line SVG supported by Firefox, Chrome, Opera 11.60 & IE9/10. It won’t work properly under Opera 11.50 & Safari on iPad thus.
This is an awesome tool using SVG. You can even play with your mouse on the custom function to edit the curve. If you’d like to know more about this tool, please again have a look to David’s article.
If your browser supports CSS3 animations, let’s now see a simple demo using easing functions to animate a canvas tag containing an animated sprite with CSS3.
Here is the CSS3 animations code that will be used in this demo:
@-ms-keyframes demo { from { -ms-animation-timing-function: ease; -ms-transform: translateX(0px); } 50% { -ms-animation-timing-function: ease-in; -ms-transform: translateX(300px); } to { -ms-animation-timing-function: ease-inout; -ms-transform: translateX(900px); } } #testCanvas { -ms-animation-delay: 0s; -ms-animation-duration: 6s; -ms-animation-iteration-count: infinite; -ms-animation-name: demo; }As well as all the vendor prefixes variations to make sure it works also in Google Chrome & Mozilla Firefox. And here’s the living output:
If your browser doesn’t support CSS3 Animation but support canvas, the sprite’s running animation should be displayed but the character won’t move through the width of the screen.
Note: if you’d like to know more about canvas and sprites animation, you can have a look to this article: HTML5 Gaming: animating sprites in Canvas with EaselJS
DelayThe “animation-delay” property simply allows an animation to begin execution some time after it is applied.
Events3 events could be raised during an animation. They are named “AnimationStart”, “AnimationEnd” and “AnimationIteration”. Depending on your browser, the correct name will be for instance:
- Chrome: webkitAnimationEnd
- Firefox: mozAnimationEnd
- Internet Explorer: MSAnimationEnd
The event will give you the following details:
- animationName: name of the animation which raised the event
- elapsedTime: the amount of time the animation has been running, in seconds
Here is an usage sample for IE10:
elementToAnimate.addEventListener("MSAnimationEnd", function () { alert("the end !"); }, false);More about CSS3 animationsCSS3 animations are really useful for 2 main reasons:
- Hardware acceleration: CSS3 Animations are most of the time directly handled by the GPU and could produce smoother results. This could then be a very interesting approach for mobile devices.
- Better separation between code and design: I know there is some debates on this point but with David, we think that a developer shouldn’t be aware of animations or anything related to design as much as possible. In the same way the designer/artist must not be aware of JavaScript. CSS3 offers then this possibility and could let the designers work with their classical tools to generate the appropriate animations on elements, between screens, etc.
To highlight this importance in performance, the following HTML5 game I wrote using a full frame <canvas>: HTML5 Platformer run at 60 fps in IE9/IE10 on my PC but at 10 fps max on a iPad 2. This is because its CPU is much more limited and the iPad is currently not hardware-accelerating <canvas>. Using CSS3 Transitions/Animations to animate several smaller <canvas> elements could provide a huge performance boost for this game. Think about it when you’re targeting mobile devices!
Browsers SupportSince the Platform Preview 3 of IE10 available in the Windows Developer Preview, we’re supporting CSS3 Animations. And as you can see on the following report produced by caniuse.com, the CSS3 animations are now supported on a wide range of browsers:
But as the specification is not finished yet (working draft), you must use vendor’s prefixes such as –ms-, –moz-, –webkit-, –o- to make a cross-browsers compatible application.
But the question could be: how to handle browsers that don’t support this new feature?
First option is to just do nothing. Thanks to the beauty of graceful degradation, you could just let the user only see a static image if you’ve worked correctly. This is for instance the case of these 2 original samples of Anthony: I Twitty the Fool! and Pure CSS3 AT-AT Walker . When watched in IE9, it looks like we only have a static image. When watched in IE10, the very same code shows nice animations. IE10 users will then have an enhanced version while IE9 will still be able to view and use properly the website. The more modern your browser is, the more visual bonus you will have.
The second option is to detect the feature via a JS library like Modernizr and try to offer the same animation via a JavaScript library that will mimic the animations. This is what we usually call a fallback mechanism. Unfortunately, I haven’t found today a working & complete JS library that could replace CSS3 animations when not supported by the browser.
I have then written a sample JS library more or less specifically designed for the AT-AT sample.
CSS3 Animations JavaScript fallback libraryAnimations are nothing more than a series of transitions separated by a certain duration defined via the keyframes. I’ve then reused the concepts built by David Catuhe in his transitions helper library. I let you reviewing his article to check the base of the concepts behind the code.
On my side, I’ve added some support to animate the CSS3 2D Transform rotation & translation values and a way to iterate through the keyframes.
Here is the main part of the library you need to review:
// Animation object // It need the HTML targeted element, the name of the animation, its duration & iteration count and // the keyframes contained in an array object // View the animation simply as a sequence of transitions played a certain number of times ANIMATIONSHELPER.animation = function (target, name, duration, iterationcount, keyframes) { // saving the properties values this.name = name; this.duration = duration; this.iterationcount = iterationcount; this.target = target; var elapsedtime = 0; var keyframeduration = 0; var elapsedtime = 0; // Transforming the percentage of each keyframe into duration value for (var i = 0; i < keyframes.length; i++) { keyframeduration = ((keyframes[i].percentage * duration) / 100) - elapsedtime; keyframes[i].duration = keyframeduration; elapsedtime += keyframeduration; } this.currentTransition = { isPlaying: false }; this.keyframes = keyframes; this.keyframesCount = keyframes.length; this.currentKeyFrameIndex = 0; // The nextTransition() function return the next transition to run // based on the current keyframe to play this.nextTransition = function (keyframe, ease, customEaseP1X, customEaseP1Y, customEaseP2X, customEaseP2Y) { var properties = []; var finalValues = []; var transition; // Compared to the original TRANSITIONSHELPER of David Catuhe // We need a specific code to play with the CSS3 2D Transform properties values if (keyframe.propertyToAnimate === "transform") { for (var i = 0; i < keyframe.transformType.length; i++) { properties.push(keyframe.transformType[i].type); if (keyframe.transformType[i].type == "rotate") { finalValues.push({ deg: keyframe.transformType[i].value1 }); } else { finalValues.push({ x: keyframe.transformType[i].value1, y: keyframe.transformType[i].value2 }); } } // Create a new transition transition = { name: this.name + this.currentKeyFrameIndex, target: this.target, properties: properties, finalValues: finalValues, originalValues: ANIMATIONSHELPER.extractValues(target.style[ANIMATIONSHELPER.currentTransformProperty], this.name), duration: keyframe.duration, startDate: (new Date).getTime(), currentDate: (new Date).getTime(), ease: ease, customEaseP1X: customEaseP1X, customEaseP2X: customEaseP2X, customEaseP1Y: customEaseP1Y, customEaseP2Y: customEaseP2Y, isPlaying: true, type: "transform" }; return transition; } // If it's a classic property to animate, we're using more or less the TRANSITIONSHELPER as-is else { return TRANSITIONSHELPER.transition(this.target, keyframe.propertyToAnimate, keyframe.value, keyframe.duration, TRANSITIONSHELPER.easingFunctions.linear); } }; // each animation object has a tick function // that will be called every 17 ms (to target 60 fps) // This ticker is monitoring the current state of the transition and // create a new transition as soon as the old one is finished/dead this.tick = function () { if (this.iterationcount > 0) { if (!this.currentTransition.isPlaying) { this.currentTransition = this.nextTransition(this.keyframes[this.currentKeyFrameIndex], ANIMATIONSHELPER.easingFunctions.linear); // We're using our own global ticker only for the 2D transformations // Otherwise, we're using the one from the TRANSITIONSHELPER library if (this.currentTransition.type === "transform") { ANIMATIONSHELPER.currentTransitions.push(this.currentTransition); } this.currentKeyFrameIndex++; // We've reached the last keyframe (100%). We're starting back from the beginning if (this.currentKeyFrameIndex >= this.keyframesCount) { this.currentKeyFrameIndex = 0; this.iterationcount--; } } } }; };The first part of the code is iterating through each keyframe to compute the exact duration specified by each percentage. We’re then defining a nextTransition() function that will dynamically build the next transition to play based on the current index into the keyframes collection. At last, we’ve got a tick() function that will monitor the current state of the transition applied. Once the transition is finished or dead, it asks for the next transition, push it to the stack of transitions to be played and moves the indexes.
This tick() function is called thanks to this code:
ANIMATIONSHELPER.launchAnimation = function (animation) { // Launching the tick service if required if (ANIMATIONSHELPER.tickIntervalID == 0) { ANIMATIONSHELPER.tickIntervalID = setInterval(ANIMATIONSHELPER.tick, 17); } // Little closure to launch the tick method on the appropriate animation instance setInterval(function () { animation.tick(); }, 17); };At last, we have this kind of code that helps us building the keyframes:
// Object to build a new generic keyframe (not working on the CSS3 2D Transform properties thus) ANIMATIONSHELPER.keyframe = function (percentage, propertyToAnimate, value) { this.percentage = percentage; this.propertyToAnimate = propertyToAnimate; this.value = value; }; //Objects to build specific rotation keyframes ANIMATIONSHELPER.rotationkeyframe = function (percentage, value) { this.percentage = percentage; this.propertyToAnimate = "transform"; this.transformType = []; this.transformType.push(new ANIMATIONSHELPER.transformType("rotate", value)); };To highlight its usage, let’s recreate the previous simple CSS3 Animation skull sample with this library :
// number of times you'd like the animations to be run var iterationsNumber = 100; var skullElement = document.getElementById("skull"); var keyframes = []; keyframes.push(new ANIMATIONSHELPER.rotationkeyframe(25, 15)); keyframes.push(new ANIMATIONSHELPER.rotationkeyframe(50, -5)); keyframes.push(new ANIMATIONSHELPER.rotationkeyframe(55, 0)); keyframes.push(new ANIMATIONSHELPER.rotationkeyframe(75, -10)); keyframes.push(new ANIMATIONSHELPER.rotationkeyframe(100, 0)); var animation1 = new ANIMATIONSHELPER.animation(skullElement, "rotate-skull", 7000, iterationsNumber, keyframes); ANIMATIONSHELPER.launchAnimation(animation1, ANIMATIONSHELPER.easingFunctions.linear);And here is the result that will now work in every browser supporting CSS3 2D Transform:
At last, the very first sample demonstrated at the beginning of this article uses Modernizr to check the support for CSS3 Animations. If it’s not the case, it loads the code that will mimic the keyframes defined in the file master.css, moz-master.css & ms-master.css :
// Checking if CSS3 animations is supported if (!Modernizr.cssanimations) { // if so, we can use our JS fallback library supportElement.innerHTML = "CSS3 Animations <strong>are not supported</strong>"; LoadJSAnimationsFallback(); } else { // if CSS3 animation is supported, we have nothing to do. // The *master.css stylesheets will be automatically applied & used. supportElement.innerHTML = "CSS3 Animations <strong>are supported</strong>"; }The LoadJSAnimationsFallback() function is defined into jsfallback-master.js which simply contains all the keyframes declarations and the 19 animations needed to mimic the behavior created by Anthony in pure CSS3. In this approach, the designer then needs to rewrite all rules using the library. Another approach could be to parse one of the CSS file using an XHR call and to create dynamically the JavaScript calls to the library. This needs more work as you almost need to reimplement the CSS3 animations specifications in JavaScript!
You now have an idea on the way to build a fallback mechanism to support more browsers while starting to use the latest CSS3 specifications.
You can download the files for the main sample here: http://david.blob.core.windows.net/html5/css3atat/CSS3ATATNonMinified.zip
It contains the unminified versions of the animationsHelper.js, transitionsHelper.js, jsfallback-master.js JavaScript files as well as the various CSS3 declinaison files for the main vendors prefixes.
ConclusionCSS3 Animations is a powerful technology to push HTML5 applications to a new level. It offers interesting scenarios. Designers could use it to create a new generation of UI screens with smooth & fluid animations without the need of developers. As it’s hardware-accelerated most of the time, developers should also pay attention to this specification. At last, both could collaborate. Designers could work on a series of predefined animations covering most scenarios. Developers could then create JavaScript libraries that will implement those animations. This library could offer in a transparent way 2 implementations: a dynamic generation of CSS3 on the fly or a fallback for older browsers.
Going further- Article about CSS3 Transitions by David Catuhe: Introduction to CSS3 Transitions
- CSS3 Animations specifications: http://www.w3.org/TR/css3-animations/
- IE Test Drive on CSS3 animations: http://ie.microsoft.com/testdrive/Graphics/hands-on-css3/hands-on_animations.htm
Other useful posts:
- Events are relatively limited in the CSS3 Animation spec. Joe Lambert is proposing an interesting solution to trigger events on each keyframe: CSS Animation Keyframe Events (Javascript solution)
Increasing Responses Using Split Testing Basics
Recently we wanted to trial two messages for lead collection. We built a landing page template, and then created two headlines and copy variations. One was using a very active tone, and one less so.
We ran a straw poll within our team, and we were fairly certain which one would work better – but how do we test this? That’s where split testing comes in! Using Google’s free Website Optimizer tool, and a humble budget to be spent on Google AdWords, we did just that.
Rather than publish a page and make a few tweaks every few months, split testing can achieve greater results in shorter time, and it doesn’t need to cost a lot. The way Google Optimizer works, is that it will allow you to send traffic to both pages, and set a goal for conversion. We had a simple contact form on these landing pages, so determined we wanted that form to be completed as our goal.
Then, using Google AdWords, we ran two Ad Groups, each pointing to one of the two landing pages. Then, we bumped up a budget so we could get at least a few dozen visitors to each page over two weeks.
The result? We found one page (the very direct, active voice one) had nearly 50% more form completions than the other. The cost ended up being under US$100 and a few hours of our time.
Now, we can roll that page on our main website, knowing with confidence we’ve chosen the right one. If we really wanted to (and I’m positive we will soon) we could then make further subtle changes, and run a second split testing campaign.
Give it a go by testing two variations of contact forms, or two variations of your homepage – you’ll be surprised how quick you’ll get some real results!
SitePoint Podcast #148: All Aboard the Facebook Train
Episode 148 of The SitePoint Podcast is now available! This week the panel is made up of Louis Simoneau (@rssaddict), Kevin Dees (@kevindees), Stephan Segraves (@ssegraves) and Patrick O’Keefe (@ifroggy).
Listen in Your BrowserPlay this episode directly in your browser — just click the orange “play” button below:
Download this EpisodeYou can download this episode as a standalone MP3 file. Here’s the link:
- SitePoint Podcast #148: All Aboard the Facebook Train (MP3, 50:21, 48.4MB)
The SitePoint Podcast is on iTunes! Add the SitePoint Podcast to your iTunes player. Or, if you don’t use iTunes, you can subscribe to the feed directly.
Episode SummaryHere are the main topics covered in this episode:
- Curebit Apologizes for Copying 37Signals: “Stupid, Lazy, and Disrespectful” | TechCrunch
- The Trouble With “Free”
- Riding Rails: Rails 3.2.0: Faster dev mode and routing, explain queries, tagged logger, store
- Facebook is Killing Local Social Networks around the World
Browse the full list of links referenced in the show at http://delicious.com/sitepointpodcast/148.
Host Spotlights- Patrick: WP Late Night – The Podcast for WordPress Users.
- Stephan: Convert Bookmarklet to Chrome Extension
- Louis: Making Love to WebKit — Acko.net
- Kevin: List.js – Examples of how to use the script
Transcript to follow.
Theme music by Mike Mella.
Thanks for listening! Feel free to let us know how we’re doing, or to continue the discussion, using the comments field below.
Browser Trends February 2012: Chrome 16 Obliterates IE8
I expected last month’s browser statistics to be a little unusual. With a large proportion of the western world on vacation, the ratio of home to business usage rises. Typically, IE usage would drop and the other browsers would rise. You’d expect IE fluctuations to stop following the return to business in January. So let’s look at the latest worldwide StatCounter statistics to see if that happened:
BrowserDecemberJanuarychangerelativeIE 9.0+10.75%11.45%+0.70%+6.50%IE 8.022.12%20.82%-1.30%-5.90%IE 7.04.00%3.63%-0.37%-9.30%IE 6.01.78%1.56%-0.22%-12.40%Firefox 4.0+19.81%20.01%+0.20%+1.00%Firefox 3.7-5.46%4.77%-0.69%-12.60%Chrome27.33%28.45%+1.12%+4.10%Safari6.09%6.61%+0.52%+8.50%Opera1.99%1.96%-0.03%-1.50%Others0.67%0.74%+0.07%+10.40%IE (all)38.65%37.46%-1.19%-3.10%Firefox (all)25.27%24.78%-0.49%-1.90%The table shows market share estimates for desktop browsers. The ‘change’ column shows the absolute increase or decrease in market share. The ‘relative’ column indicates the proportional change, i.e. another 12.4% of IE6 users abandoned the browser last month. Party time! There are several caveats so I recommend you read How Browser Market Share is Calculated.
I previously reported that, despite widespread reports, Chrome 15′s victory as the world’s most-used browser version was short-lived. The release of Chrome 16 on December 13 2011 split the user base so IE8 quickly retained its lead.
However, we’re now at the end of Chrome 16′s life. In fact, Chrome 17 is around a week overdue so the vast majority of Chrome users — 25.79% — are using version 16 (I was tempted to refer to it as ‘old’ but it’s hardly past its prime at seven weeks of age!) The next release will split Chrome’s user base again, but IE8 is losing ground too rapidly to keep up. It lost another 1.3% in January after a 1.88% drop the month before. The browser looks likely to dip below 20% during the next few weeks.
Although IE9 had a good month, overall, IE dropped 1.19%. Firefox also lost half a percent. While most of those users switched to Chrome, Safari also received a surprise boost. Did you receive a new Mac or iPad during the holidays? If so, perhaps you’re partially responsible for that half-percent jump.
Chrome is looking unbeatable. It’s monthly 1% rise is holding firm and it’s likely to overtake IE by the middle of the year.
Mobile Browser UsageThe mobile market remained busy during January and usage accounted for 8.49% of all web activity.
The primary mobile browsing applications are:
- Opera Mini/Mobile — 23.34% (down 0.88%)
- Android — 21.39% (up 1.17%)
- iPhone — 19.51% (up 1.10%)
- Nokia browser — 11.82% (down 1.10%)
- Blackberry — 6.68% (down 0.85%)
There’s little point reading too much into these figures; there are too many handset harlots switching phones more frequently than their underwear! The only obvious trend is Blackberry’s continuing misfortunes; but you don’t need browser statistics to see that.
What’s New in Firefox 10
Firefox reached double-digit version numbers on January 31, 2012. It’s been a mere six weeks since we received Firefox 9.0 but there are several interesting features in the new version…
Page InspectorFirefox now has it’s own built-in page inspector. To launch it, select Inspect from the Web Developer menu, point to an element and choose “Inspect” from the right-click context menu, or press Ctrl+Shift+I. The active element will be highlighted and a bar appears at the bottom of the window showing a clickable breadcrumb trail of the DOM hierarchy.
The HTML button shows the element’s location in the page source while Style displays an editable list of applied CSS rules and properties.
Let’s be honest: it’s not Firebug. But it looks good, works well and will be invaluable on those rare occasions when Firebug isn’t available. I was a little concerned the Inspector would clash with Firebug in some way but I’m yet to encounter a problem. CSS 3D Transforms
Like the webkit browsers, Firefox 10 now supports CSS 3D transforms which allow you to rotate, skew and translate objects in three-dimensional space. You’ll require the -moz prefix but I suspect more developers will experiment with the effects now they’re supported in Firefox, Chrome, Safari and IE10.
Fullscreen APIThe fullscreen API is another webkit feature which has been implemented in Firefox 10. You guessed correctly — it allows a page to launch the browser in full-screen mode which makes it ideal for videos, games and other interactive media.
Before hackers and pop-up advertisers become too excited, you should note that the browser places several restrictions on the feature and users cannot be placed in fullscreen mode without their knowledge or consent. The API is not particularly stable; the W3C Fullscreen specification is an early draft and there are minor differences between the webkit and gecko implementations.
Visibility APIThe visibility API allows you to check three new document true/false properties:
- visible — your document is the foreground tab of a non-minimized window
- hidden — your document is either a background tab or on a minimized window
- prerender — your document is being pre-rendered and is not visible to the user
It’s simple but allow us to make more efficient and usable web pages. For example, changing tabs could automatically pause a video or slow down Ajax requests.
Dynamic Forward ButtonThe Forward navigation button now automatically hides itself when there’s no page to forward too! I’ve not heard anyone complain about wasted toolbar space, but it’s a logical interface enhancement since the button’s rarely used.
Better Add-On CompatibilityProbably. Until version 10, Firefox assumed add-ons were incompatible if they were marked as valid for an earlier version. This was rarely the case and you could often force an add-on to install by changing the supported browser number. Mozilla has now reversed the policy; any extension which is compatible with Firefox 4.0 and doesn’t contain compiled code is presumed to be compatible.
Add-on issues have not been stopped completely, but the situation has improved considerably since last year.
Overall, Firefox 10 is a solid release with a good number of features. Say goodbye to version 9.0 and upgrade today … because version 11 will be with us on March 13, 2012.
Using the HTML5 Geolocation API
Knowing the location of your users can help boost the quality of your website and the speed of your service.
In the past, users had to actively input their location and submit it to a site, either by typing it, using a long drop-down list, or clicking a map. Now, with the HTML5 Geolocation API, finding your users (with their permission) is easier than ever.
Figure 1 shows a website using geolocation to determine the location of a user, represented in latitude and longitude. The numbers can easily be translated into something more understandable, such as the street name or city.
Figure 1 Showing a User’s Location with the Help of Geolocation
Imagine how useful your site could be if it provided online timetables for all public transportation in a particular city. Using geolocation, the site could recommend optimal travel routes to get people where they’re going as quickly as possible. Desktop users could get their start location sorted by proximity to their computer. Mobile users trying to get home after a night out could quickly find the closest bus stop within walking distance. These possibilities and more are just an API away. Scenarios for Using the Geolocation API
Here are 12 simple scenarios that illustrate how a website can accommodate users and customize their experience by taking their location into account. Some of them might seem obvious, but the small things often make the biggest differences.
- Public transportation sites can list nearby bus stops and metro locations.
- Late night out? Taxi or car service websites can find where you are, even if you don’t know.
- Shopping sites can immediately provide estimates for shipping costs.
- Travel agencies can provide better vacation tips for current location and season.
- Content sites can more accurately determine the language and dialect of search queries.
- Real estate sites can present average house prices in a particular area, a handy tool when you’re driving around to check out a neighborhood or visit open houses.
- Movie theater sites can promote films playing nearby.
- Online games can blend reality into the game play by giving users missions to accomplish in the real world.
- News sites can include customized local headlines and weather on their front page.
- Online stores can inform whether products are in stock at local retailers.
- Sports and entertainment ticket sales sites can promote upcoming games and shows nearby.
- Job postings can automatically include potential commute times.
Technically speaking, a PC or a mobile device has several ways to find out its own location (hopefully, in the same place as the user).
- GPS is the most accurate way to determine positioning, but it’s less energy efficient than other options and sometimes requires a lengthy startup time.
- A-GPS (assistive GPS) uses triangulation between mobile phone towers and public masts to determine location. Although not as precise as GPS, A-GPS is sufficient for many scenarios.
- Mobile devices that support Wi-Fi access points can use hotspots to determine the user’s location.
- Stationary computers without wireless devices can obtain rough location information using known IP address ranges.
When it comes to sharing the physical location of users, privacy is a serious concern. According to the Geolocation API, “user agents must not send location information to websites without the express permission of the user.” In other words, a user must always opt in to share location information with a website.
Figure 2 shows a typical message requesting a user’s permission. For more information about ensuring security with the Geolocation API, see Security and privacy considerations.
Figure 2 Sample User Permission Request
Three Simple FunctionsAre you ready to incorporate geolocation into your website? You need to learn only three simple functions to master the entire API, which resides within the geolocation object, an attribute of the Navigator object. Learn more about the geolocation object here.
The getCurrentPosition function gets the user location one time. It takes two arguments in the form of callbacks: one for a successful location query and one for a failed location query. The success callback takes a Position object as an argument. It optionally takes a third argument in the form of a PositionOptions object.
navigator.geolocation.getCurrentPosition(locationSuccess, locationFail); function locationSuccess(position) { latitude = position.coords.latitude; longitude = position.coords.longitude; } function locationFail() { alert(‘Oops, could not find you.’); }The Position object contains the properties shown below.
Properties of the Position Object
PropertyValueUnitcoords.latitudedoubledegreescoords.longitudedoubledegreescoords.altitudedouble or nullmeterscoords.accuracydoublemeterscoords.altitudeAccuracydouble or nullmeterscoords.headingdouble or nulldegrees clockwisecoords.speeddouble or nullmeters/secondtimestampDOMTimeStamplike the Date objectThe watchPosition function keeps polling for user position and returns an associated ID. The device determines the rate of updates and pushes changes in location to the server.
The clearWatch function stops polling for user position. It takes the ID of watchPosition as an argument.
Presenting Location Data: Geodetic or CivicThere are two ways of presenting location data to the user: geodetic and civil. The geodetic way of describing position refers directly to latitude and longitude. The civic representation of location data is a more human readable and understandable format.
Each parameter has both a geodetic representation and a civic representation, as illustrated below.
Examples of Geodetic and Civic Data
AttributeGeodeticCivicPosition59.3, 18.6StockholmElevation10 meters4th floorHeading234 degreesTo the city centerSpeed5 km / hWalkingOrientation45 degreesNorth-EastWhen using the Geolocation API, you get the geodetic data back from the functions. Presenting location data in raw numbers is rarely friendly or useful. Online services, such as Bing Maps and Yahoo GeoPlanet can help you translate between the two presentation modes.
Browser SupportInternet Explorer 9+Firefox 3.5+Chrome 5+Opera 10.6+Safari 5+iPhone 3+Android 2+Windows Phone 7.5+Browsers that support the HTML5 Geolocation APIEven though geolocation works in all the major browsers, you still have to take into account the scenarios in which location can’t be provided. For example, a user might be running an older browser or have hardware that doesn’t include positioning devices, or simply might not want to automatically share location information. The location detected could even be incorrect. In such situations, you should always include an alternative or a fallback method so users can enter or change their location manually.
Geolocation in ActionCopy and paste the example code below and save it as an HTML file. Open it in your favorite browser and follow the two-step instructions on the website to see the Geolocation API draw a blue circle around your current location.
Using the Geolocation API
<!doctype html> <html lang="en"> <head> <title>Geolocation demo</title> <meta charset="utf-8" /> </head> <body> <h1>Geolocation demo</h1> <p> Find out approximately where you are. </p> <p> Step 1: <button onclick="GetMap()">Show map</button> </p> <p> Step 2: When prompted, allow your location to be shared to see Geolocation in action </p> <div id="mapDiv" style="position: relative; width: 800px; height: 600px;"></div> <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script> <script type="text/javascript"> var map = null; function GetMap() { /* Replace YOUR_BING_MAPS_KEY with your own credentials. Obtain a key by signing up for a developer account at http://www.microsoft.com/maps/developers/ */ var cred = "YOUR_BING_MAPS_KEY"; // Initialize map map = new Microsoft.Maps.Map(document.getElementById("mapDiv"), { credentials: cred }); // Check if browser supports geolocation if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(locateSuccess, locateFail); } else { alert('I\'m sorry, but Geolocation is not supported in your current browser.'); } } // Successful geolocation function locateSuccess(loc) { // Set the user's location var userLocation = new Microsoft.Maps.Location(loc.coords.latitude, loc.coords.longitude); // Zoom in on user's location on map map.setView({ center: userLocation, zoom: 17 }); // Draw circle of area where user is located var locationArea = drawCircle(userLocation); map.entities.push(locationArea); } // Unsuccessful geolocation function locateFail(geoPositionError) { switch (geoPositionError.code) { case 0: // UNKNOWN_ERROR alert('An unknown error occurred, sorry'); break; case 1: // PERMISSION_DENIED alert('Permission to use Geolocation was denied'); break; case 2: // POSITION_UNAVAILABLE alert('Couldn\'t find you...'); break; case 3: // TIMEOUT alert('The Geolocation request took too long and timed out'); break; default: } } // Draw blue circle on top of user's location function drawCircle(loc) { var radius = 100; var R = 6378137; var lat = (loc.latitude * Math.PI) / 180; var lon = (loc.longitude * Math.PI) / 180; var d = parseFloat(radius) / R; var locs = new Array(); for (x = 0; x <= 360; x++) { var p = new Microsoft.Maps.Location(); brng = x * Math.PI / 180; p.latitude = Math.asin(Math.sin(lat) * Math.cos(d) + Math.cos(lat) * Math.sin(d) * Math.cos(brng)); p.longitude = ((lon + Math.atan2(Math.sin(brng) * Math.sin(d) * Math.cos(lat), Math.cos(d) - Math.sin(lat) * Math.sin(p.latitude))) * 180) / Math.PI; p.latitude = (p.latitude * 180) / Math.PI; locs.push(p); } return new Microsoft.Maps.Polygon(locs, { fillColor: new Microsoft.Maps.Color(125, 0, 0, 255), strokeColor: new Microsoft.Maps.Color(0, 0, 0, 255) }); } </script> </body> </html>If you run the code as is, your location will be shown along with a message about invalid credentials, as shown in Figure 3. To get a result without the warning text (Figure 4), you need to replace YOUR_BING_MAPS_KEY with your own key, which is generated when you sign up for a Bing Maps Developer account.
Figure 3 Geolocation Demo Mapping a Location without a Valid Key
Figure 4 Geolocation Demo Mapping a Location after Inserting a Valid Key
To see other examples of geolocation, which map your location using a push pin, visit IE Test Drive or attend an HTML5 Web Camp.
Learn more about geolocation here:
How to Avoid Retainer-client Burnout
Do you have clients on retainer? If so, you’re probably familiar with the concept of retainer-client burnout.
Retainer arrangements can seem like the ultimate freelance goal—after all, who doesn’t want a reliable, bread-and-butter income to support their freelancing? Yet retainers can wear pretty thin over time.
Let’s face it: bread and butter isn’t the most exciting meal. Many of us freelance because we like variety. Balancing our need for regular, reliable income against the potential for boredom can be difficult. But the last thing you want is to underservice or lose interest in your retainer clients—they’re more likely to be the ones you want to look after best of all.
How can you ensure you don’t get bored by retainer work, and keep your quality levels high, over the months and years?
Change the scheduleWhen you first begin a retainer, you may decide to schedule the work for the same time slot each week or month. It makes sense: this way, you can ensure you’ll always complete the work required.
That’s all good … until the retainer starts to feel a bit humdrum or boring. At those times, simply changing your schedule can have a surprising impact on your motivation for the work.
Don’t just look at moving the retainer time to a different day of the week—consider breaking the work itself up differently too. By chunking tasks differently—potentially into smaller, more specific blocks—you can find you have greater focus and can produce work of even higher quality for those regular clients.
Streamline and improve serviceOnce you have a retainer up and running smoothly, you may find it a good testing ground in which to try streamlining your work processes to make them more efficient.
The more efficiently you can complete the retainer work, the more profitable it’ll be for you. And if you find you have extra time in the retainer, that might give you the opportunity to expand the nature of your retainer work with that client.
But that’s not all! You can also use retainer relationships to do non-intrusive, ongoing, qualitative market research. Work closely and regularly with a retainer client, and you’ll likely build significant rapport.
That strong, ongoing relationship will support your evolving understanding of the retainer client’s business, needs, expectations, and industry as a whole. And that can give you deep insights into offerings you can provide to other businesses operating in the same space.
ExperimentOne thing I’ve found retainers to be great for is experimentation. Over time, you’ll likely find that you get into a groove with the routine of your retainer work, and it begins to take less time as you naturally streamline the way you work.
Why not use that extra time to try new things? This isn’t about treating retainer clients as guinea pigs; it’s about offering them added value and innovation ahead of your other clients, all within their regular retainer fee.
Similarly, your growing knowledge of your retainer client’s business and needs can help you perceive other opportunities for your services to add value to their work. These may not be opportunities that increase your retainer income, but they may keep you interested and motivated as the months pass.
Manage your rateMoney’s another, pretty compelling motivator. Yet retainer arrangements usually involve a volume discount on our standard hourly rates. While that makes sense, it can make it hard to get motivated to do retainer work when we have (or want to chase!) a nice lucrative project at the same time.
To that end, it’s probably wise to keep track of your retainer rates—don’t set them and forget them! Each time you raise your hourly rate, review your retainers as well, and make sure you tweak them accordingly.
Finally, if you find you’re losing interest in a retainer, rates can be a good first place to start diagnosing the problem: do you think the work is worth your time? If not, you may need to up your retainer rate.
Keep in close contactFor some of us, project work tends to involve more intense client contact than does retainer work. But if you like that contact, you’ll want to make sure you keep it up with retainer clients, too. Otherwise, months down the track on a retainer, you might find yourself feeling like a machine, cranking out the same old widget each week—a widget that the client has come to take entirely for granted.
A better option is to keep in close contact with your retainer clients. While it’s true that they and you both want the arrangement to be hassle-free, it’s an error to equate contact with hassle. Keeping in touch is essential for effective collaboration, and your ability to provide the best possible value for the retainer fee.
Continuously build a relationship with your retainer client—not just their business!—and you’ll likely find retainer work far more satisfying than if you work as autonomously as possible.
These are just a few ideas, but I’d love to hear how you avoid retainer burnout in your work. Share your thoughts with us in the comments.
Image courtesy stock.xchng user saavem.
DealFuel is Here
DealFuel is here. The best thing to happen since bearded dragons learnt how to play ant crusher :) And we’re kicking things off with 4 cool tech deals for:
- Web devs who want to profit from their talent
- jQuery wanna-bees and students
- Designers who are keen to work faster
- Website owners who demand peace-of-mind
There’s something for everyone!
We look forward to seeing you on DealFuel, and welcome your feedback and comments (below)
jQuery 1.7.2 Beta 1 Released
Hey there Internets, it’s the jQuery Core team! We haven’t talked in a while, but over the holidays we were busy fixing the bugs you reported. The result of that hard work is jQuery 1.7.2 Beta 1. We decided to get a beta out by Groundhog Day so you wouldn’t be in the shadow of six more weeks of unfixed bugs.
You can get the code from the jQuery CDN:
Oh, we know what you’re thinking: “Cool, a new version of jQuery; I’ll wait until the final release has been out a few weeks and then I’ll give it a try.” Right, and then you’ll find some bug that keeps you from upgrading. Nothing makes us sadder than finishing up a release and only then seeing a report of a serious bug that could have been fixed earlier.
So please, come out of your burrow and try this beta with your code. Did we miss an old bug? Did we create a new bug that makes you feel like Bill Murray waking up to “I Got You Babe?” We want to know. You can use the bug tracker to report bugs; be sure to create a test case on jsFiddle so we can figure it out easily. If you’re not sure it’s a bug, ask on our forum or on StackOverflow.
jQuery 1.7.2b1 Change LogThe current change log of the 1.7.2b1 release.
Ajax- #10978: jQuery.param() should allow non-native constructed objects as property values
- #5571: Allow chaining when passing undefined to any setter in jQuery
- #10692: Configure the jshint options to more accurately match the style guide
- #10902: ability to test a built version of jQuery in unit tests
- #10931: Unit tests shouldn’t require internet access
- #10466: jQuery.param() mistakes wrapped primitives for deep objects
- #10639: outerWidth(true) and css(‘margin’) returning % instead of px in Webkit
- #10754: have jQuery.swap return the return of the callback instead of just executing it
- #10782: Incorrect calculating width
- #10796: Bug in IE7 with $(‘#el’).css.(‘background-position’)
- #10858: css.js regular expressions are incomplete
- #11119: The curCSS function only need 2 arguments
- #8498: Animate Hooks
- #10006: method show is not working as expected in all browsers when called for document fragment
- #10848: Animation toggling loses state tracking in certain atomic edge cases
- #8165: .live(‘click’, handler) fires on disabled buttons with child elements in Chrome
- #10819: Eliminate “this.on.call(this, “
- #10878: $(“select”).live(“change”, function(){ …broken in IE8 in jQuery 1.7
- #10961: Error in XRegExp using jQuery 1.7.1 in IE6-9
- #10970: The .on() selector parameter doesn’t work with :not(:first) selector
- #10984: Cannot off() custom events ($.event.special)
- #11021: Hover hack mangles a namespace named “hover”
- #11076: .clone(true) loses delegation filters
- #11130: jQuery.fn.on: binding map with null selector ignores data
- #11145: $(document).on() not working with name=”disabled”
- #9427: Passing undefined to .text() does not trigger setter
- #10753: inline the evalScript function in manipulation.js as it’s only used once
- #10864: text() method on a document fragment always returns the empty string
- #11055: Update HTML5 Shim elements list to support latest html5shiv
- #10952: .fired() doesn’t work on Callbacks object when it is flagged with “once”
- #11257: Wrong path to source files in test suite if PHP missing
- #11048: Support Tests affect layout for positioned elements in IE6-9
5 Tips to Improve Your Design Sign-Off Process
Everyone knows good design when they see it.
Unfortunately, everyone has a different opinion about what “good design” actually is. This is a problem if you’re creating products, software, graphics or other media for a client. Your design may need to be agreed by multiple people all with their own notions and prejudices about how the product should look, feel and work. If you’re banging your head against the wall in frustration, here are five tips which could help…
1. Outline Your ProcessWalk your client through your design process. In the web sphere, this could be:
- collate the requirements and objectives
- devise concepts, walkthroughs, story boards and wireframes
- produce a final mock-up or prototype for approval
A little tweaking at all stages should be expected, but avoid falling into iterative traps; i.e. the client demands 27 different concepts, scavenges their favorite parts of each and creates a Frankenstein design which has little hope of satisfying the original requirements.
2. Avoid Design-by-CommitteeIdeally, your final design should be signed off by one person — two at most.
Unfortunately, many organizations have a culture where employees are afraid to make mistakes; it’s safer to sit on the fence than take responsibility for a decision. You may encounter situations where a decision is reached by compromise: half liked the blue design, half liked the red, so they settled on purple (which no one liked).
The problem is exacerbated by design meetings. Meetings can be dominated by one or two people who force their opinion on others or use the forum as a battleground for political posturing. The design suffers and the opinions of quieter members are never heard.
3. Approach Decision Makers IndividuallyIf the final sign-off absolutely must be agreed by multiple people, approach them individually. You can explain why the design satisfies the original objectives on a one-to-one basis and collect feedback. It gives everyone a voice, prevents internal politics and documents the responses. It’s also makes it harder to raise objections at a later stage.
Obviously, this can take more time than a single meeting but it’s less likely lead to design compromises. Rather than performing your presentation multiple times, you could consider creating a video or slideshow. That should reduce the effort required and it’s impossible for viewers to interrupt!
4. Ask Direct Questions“What do you think of the design?” is the worst question you can ask (especially by email). It turns an objective critique into a subjective discussion. People will resort to their gut instinct or first impression; you’ll rarely get anything more informative than “I liked it” or — worse — “I didn’t like it”.
Ask direct questions such as:
- Does the design satisfy requirement X?
- Does the design meet the defined business objectives?
- Does the design implement all features outlined in the wireframes?
This makes it easier to identify and document specific issues. Avoid obliging decision makers with open-ended discussions: if they can’t pinpoint a problem accurately and concisely, that problem doesn’t exist.
5. Use the Wisdom of CrowdsIf the client steadfastly refuses to appoint a single decision maker, you could consider taking the process to the other extreme. Ask everyone’s opinion: all company employees, their customers, website visitors, passers by, social media users, etc.
A decision can be made by poll statistics; it’s difficult for an individual to complain if 94% of respondents stated the design satisfied all objectives.
Please share your sign-off scare stories. Was approval expected from 93 people? Did a client dither for months? Did a lovely original concept turn into a monster? Was a design rejected because Amy in Accounts didn’t like a shade of green which reminded her of broccoli?
Comments welcome…
Serving Static Files with Node.js
In this article we’re going to look at setting up a static file server using Node. There’s plenty of modules available to help us do this, but we’re going to focus on three of them: node-static, paperboy, and http-server.
All of these modules are available via npm (Node Package Manager). npm has been packaged with node since version 0.6.3, but if you haven’t got it installed it’s easy enough to do with this one-liner:
curl http://npmjs.org/install.sh | shLet’s have a look at each static file server in turn, starting with node-static.
node-staticnode-static can be installed via npm with the following command:
npm install node-staticTo use it: include the module in your script via the require function, create a server, pass it your preferred options, and tell it to serve files:
var static = require('node-static'), http = require('http'), util = require('util'); var webroot = './public', port = 8080; var file = new(static.Server)(webroot, { cache: 600, headers: { 'X-Powered-By': 'node-static' } }); http.createServer(function(req, res) { req.addListener('end', function() { file.serve(req, res, function(err, result) { if (err) { console.error('Error serving %s - %s', req.url, err.message); if (err.status === 404 || err.status === 500) { file.serveFile(util.format('/%d.html', err.status), err.status, {}, req, res); } else { res.writeHead(err.status, err.headers); res.end(); } } else { console.log('%s - %s', req.url, res.message); } }); }); }).listen(port); console.log('node-static running at http://localhost:%d', port);Here, when we create a new instance of a node-static server, we pass it:
- the directory we want it to serve files from by way of the `webroot` variable (node-static defaults to serving files from the current directory unless you tell it otherwise)
- a cache value of 600, so each file served will be cached for 10 minutes (the default value for cache is 3600, meaning files are cached for 1 hour)
- a custom ‘X-Powered-By’ header (I’ve used X-Powered-By solely as an example – you may, or may not, want to disclose the software you’re server is running)
We then use the http module’s createServer function, and add an event handler for the request’s end event where we use our instance of node-static to serve the files.
When we call file.serve, we pass it an optional callback function that let’s us customise how we handle any errors:
- we check err.status, and serve either 404.html or 500.html accordingly
- if there’s an error and the status code is something other than 404 or 500, we send the status code and the headers back to the client explicitly – when you pass a callback function to file.serve and there is an error, node-static will not respond to the client by itself.
paperboy provides a fluent API for setting up your server that lets you configure the port and IP address, set HTTP headers, and handle events pre-request and post-response. You can also tell it what to do in case of a error. Let’s see how we might use paperboy to create a static file server:
var paperboy = require('paperboy'), http = require('http'), path = require('path'); var webroot = path.join(__dirname, 'public'), port = 8080; http.createServer(function(req, res) { var ip = req.connection.remoteAddress; paperboy .deliver(webroot, req, res) .addHeader('X-Powered-By', 'Atari') .before(function() { console.log('Request received for ' + req.url); }) .after(function(statusCode) { console.log(statusCode + ' - ' + req.url + ' ' + ip); }) .error(function(statusCode, msg) { console.log([statusCode, msg, req.url, ip].join(' ')); res.writeHead(statusCode, { 'Content-Type': 'text/plain' }); res.end('Error [' + statusCode + ']'); }) .otherwise(function(err) { console.log([404, err, req.url, ip].join(' ')); res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Error 404: File not found'); }); }).listen(port); console.log('paperboy on his round at http://localhost:' + port);After importing the modules we require, we use a couple of variables to store the webroot we want documents served from, and the port we want our server to listen on. Note that the current version of paperboy (0.0.3) doesn’t appear to support a webroot specified with a relative filepath e.g. ./public. We therefore use path.join(__dirname, ‘public’) to give us the absolute path to the public folder we want to serve files from (__dirname is one of Node’s globals and gives us the name of the directory that the currently executing script lives in).
We pass webroot to the deliver function so paperboy knows which directory to serve files from. addHeader then adds some HTTP headers. It takes two arguments: the name of the header, and the header’s value. We then use the before, after, error, and otherwise functions to add event handlers via some callback functions:
- before will fire it’s callback function just before the requested file is delivered. To stop the file being served you can return false from the callback function.
- after will fire it’s callback function once a response has been sent back to the client and the file has been delivered. The callback function is passed a single argument representing the status code of the HTTP response sent back to the client
- error fires if something goes wrong during the processing of the request. The callback is passed two arguments: the HTTP status code of the response, and the error message. Note that you’ll need to close the connection to the server yourself in your error callback function; the example above does this with the line response.end(…).
- otherwise fires if no matching file was found in webroot, or if false was returned from the before callback function. The callback function is passed a single argument containing details of the error. paperboy’s default callback function shows a simple HTTP 404 File Not Found page.
Lastly we tell the server which port to listen on. Note that when it receives a request for ‘/’, paperboy appends index.html and serves that file by default, as long as index.html exists.
http-serverFinally, lets have a look at http-server. Installation is done via npm, but make sure you pass npm the -g switch to make http-server available globally:
npm install http-server -gInstalling http-server this way makes it available via the command line:
$> type http-server http-server is hashed (/usr/local/bin/http-server)As it’s available from the command line, you won’t have to write any code to get it up and running. All you have to do is type:
$> http-server Starting up http-server, serving ./public on port: 8080 http-server successfully started: http://localhost:8080 Hit CTRL-C to stop the serverThis will start the serving files out of the ./public directory if it exists, or the current working directory if it doesn’t. The default port used by http-server is 8080, but you can change this, and a few other options, via command line switches:
- -p to specify which port to listen on
- -a to tell it which IP address to bind to
- -i to configure whether to display autoIndex (defaults to true)
- -s or –silent to turn off console logging
- -h or –help to display this list of available commands
In this article, we’ve looked at three static file server modules available in Node: node-server, paperboy, and http-server. While they all achieve the same goal, which one you choose to use might be down to how much code you want to write, and how much control you need to exercise, over your Node static file server.
Of course, you might decide to write your own static file server from scratch. If you do, you could do a lot worse than to peruse the source code of the three servers we’ve looked at today:
- node-server – https://github.com/cloudhead/node-static
- paperboy – https://github.com/felixge/node-paperboy
- http-server – https://github.com/nodeapps/http-server
What’s on at PHPMaster?
We’ve been busy over at PHPMaster, working to bring you the best articles and tutorials so you can get to know one of the most widely used languages on the Web
Under the Hood of Yii’s Component Architecture, Part 1This is Part 1 of a 3-part series that shows you some of the inner-workings of the Yii Framework’s component architecture. In this part you’ll learn how Yii’s CComponent class uses PHP’s magic __get() and __set() functions to take advantage of the benefits of getter and setter methods while still allowing access to properties as if they were public variables.
Pagination with CodeIgniterPagination is useful when displaying a large dataset which might have hundreds results and provides a much nicer user experience. In this tutorial, you’ll learn how to use CodeIgniter’s pagination library to create a paginated list of results from a MySQL database.
Bending XML to Your WillExtensible Markup Language (XML) is a big building block of today’s web with hundreds of XML-based languages having been developed, including XHTML, ATOM, and SOAP just to name a few. Knowing how to process XML data is a crucial programming skill today, and thankfully, PHP offers multiple ways to work with it. In this article you’ll learn what XML is and how to use the XML Parser and SimpleXML extensions to parse it. The Liskov Substitution Principle
The hard fight against mean machines that enjoy enslaving humans and using them like plain batteries will hopefully end up in a resounding triumph… if only Neo adheres to the Liskov Substitution Principle.
Rapid Application Development with CakePHPCakePHP is a framework that provides a solid MVC base for PHP development, allowing users at all skill levels rapidly develop robust web applications. In this article you’ll learn about two of CakePHP’s most useful features: automatic code generation with Bake, and dynamic scaffolding.
“Un-Selling” Another’s Solution? Shame on You!
An SEO client of yours is approached by another web company with a unique marketing solution which you cannot provide. When your client asks your opinion, you …
- Take an unbiased look and give the client your honest advice
- Tell your client this would “conflict” with the work you’re doing for fear that, if your client begins working with another web firm, you might wind up losing their business
In my last article, Transactional vs. Consultative Selling: Knowing the Difference Makes All the Difference, I compared the transactional sale with the consultative one:
Transactional SaleA simple, short-term sale in which the customer already knows what he needs. Little or no product knowledge is required on the sales side. Buying criteria is usually based on “how much?” or “how fast can I get it?”
Consultative SaleA complex, long-term sale involving the collaboration of both buyer and seller. The sales person must first understand the customer’s needs before offering a solution.
It becomes a problem when a prospect attempts to engage our services using the transactional approach. Yet, we can do our clients an equal dis-service when we assume the transactional approach, but disguise it as consultative.
Harvard Business School professor, Ranjay Gulati, explores the fallacy that media companies are “consultative and customer focused.” According to the article on BIA/Kelsey’s Local Media Watch blog, salespeople are saying “I’ll talk about your needs so long as it leads to you only buying my portfolio of solutions …” but that they are “communicating with customers through a product lens (with a pre-determined end in mind).” That’s a problem.
Consultative or collaborative selling is about transparency and building solutions that fit the customer’s needs and not necessarily the media company’s balance sheet. If a salesperson is aiming to sell a specific product set, and is willing to un-sell other potential solutions, then this version of consultative selling is merely disguised as the same transactional selling of old … (Italics Mine)
Are you willing to walk away rather than propose a solution that won’t truly meet the prospect’s needs? Or will you “un-sell” another potential solution by providing disinformation to convince the prospect that your offering is superior to another type of advertising or marketing medium?
Incidentally, the scenario I shared at the beginning of this article actually happened to one of our sales reps, and his client’s SEO “consultant” chose Option B. Shame on you, whoever you are.
How to Put Your CSS3 on :target
CSS pseudo-classes which change styles in response to user actions have been with us for many years. You’ve almost certainly used :hover, :active and :focus; I can (only just) remember the excitement of changing IE4′s link colors back in 1997.
CSS3 also introduced :target; a powerful pseudo-class which can reduce the need for scripting in interactive widgets. Consider a page URL such as http://mysite.com/page#mytarget; an element with the id “mytarget” can have matching :target styles applied.
:target Browser SupportAll modern browsers support :target and you won’t experience problems with IE9 or most versions of Chrome, Firefox, Safari and Opera. Unfortunately, that still leaves us with the older versions of IE. I wouldn’t worry too much about IE6 and 7, but IE8 remains the world’s most used browser version. All is not lost, however, since shims such as selectivizr can add :target support without requiring complex workarounds.
A Simple Document :targetWe’ve recently been discussing website contracts. Generic contract small print such as payment schedules, hosting conditions, cancellation terms, support policies, glossaries etc. could be contained in one or more web pages. The document could grow to a considerable length even if you try and keep it concise!
Let’s look at a snippet of the document’s HTML5 in contract.html:
<h1>Website Contract</h1> <nav> <ul> <li><a href="#payment">Payment Schedule</a></li> <li><a href="#support">Support & Maintenance</a></li> <li><a href="#hosting">Hosting Terms</a></li> <li><a href="#glossary">Glossary</a></li> </ul> </nav> <article id="payment"> <h2>Payment Schedule</h2> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p> </article> <article id="support"> <h2>Support & Maintenance</h2> <p>Ut euismod tempor porttitor.</p> </article> <article id="hosting"> <h2>Hosting Terms</h2> <p>Suspendisse ac nisl lorem, ut fermentum erat.</p> </article> <article id="glossary"> <h2>Glossary</h2> <p>Aenean id nibh eget nisl blandit hendrerit lobortis ac tortor.</p> </article>We can use the :target attribute to highlight active sections, e.g.
article:target { background-color: #ffc; border: 2px solid #fcc; }Anyone viewing the contract can click a navigation menu item to highlight the appropriate section. You can also issue direct links to clients who require specific information, e.g. contract.html#support.
The :target selector offers further versatility — it’s possible to create dynamic effects in HTML5 and CSS without using JavaScript. Further SitePoint articles are coming soon…
Responsive Web Design
It all started with Responsive Web Design, an article by Ethan Marcotte on A List Apart. Essentially, the article proposed addressing the ever-changing landscape of devices, browsers, screen sizes and orientations by creating flexible, fluid and adaptive Websites. Instead of responding to today’s needs for a desktop web version adapted to the most common screen resolution, along with a particular mobile version (often specific to a single mobile device), the idea is to approach the issue the other way around: use flexible and fluid layouts that adapt to almost any screen.
Core Concepts
Three key technical features are at the heart of responsive web design:
- Media queries and media query listeners
- A flexible grid-based layout that uses relative sizing
- Flexible images and media, through dynamic resizing or CSS
Truly responsive web design requires all three features to be implemented.
The key point is adapting to the user’s needs and device capabilities. Suppose a mobile user will be viewing your site on a small screen. Taking the user’s needs into account doesn’t just mean adapting your content to the screen size. It also means thinking about what that mobile user will require first when visiting your site and then laying out the content accordingly. Maybe you’ll present the information in a different order. Don’t assume the user won’t need access to all the site information because she’s on a mobile device. You might need to change the fonts or interaction areas to respond better to a touch environment. All these factors influence responsive web design.
While mobile devices are changing the display landscape, with the appearance of more and more small screens, don’t forget what’s happening at the other end of the spectrum. Displays are also getting larger and larger. Having to serve both segments shouldn’t stop designers from being innovative on either.
Media QueriesStarting with CSS 2.1, media types were used to apply CSS for both screen and print. You might remember these media types:
<link rel="stylesheet" type="text/css" href="style.css" media="screen" /> <link rel="stylesheet" type="text/css" href="printfriendly.css" media="print" />That was it! Luckily, the W3C improved media queries in CSS3, moving them a big step forward.
Today, you can use media queries to scope styles to specific capabilities, applying different styles based on the capabilities that match your query. You can even combine queries that test for several features by using semantic operators such as AND and NOT). Features include width, height, max-width, max-height, device-height, orientation, aspect-ratio, resolution and more.
There are three ways to implement media queries:
- Use the @import rule to import style rules from other style sheets:
- @import url(style600min.css) screen and (min-width: 600px);
- Put media queries directly in the style sheet, as shown below.
Include a query in a linked style sheet’s media attribute:
<link rel="stylesheet" type="text/css" media="screen and (max-device-width: 800px)" href="style800.css" />
Because of the (cascading) nature of CSS, default styles are defined at the top with the media query matching rules and styles below. Styles defined at the top will be cascaded to the matching styles in the rule, or even completely overwritten.
The following images present an example of a responsive web design approach that uses media queries.
Figure 1 and Figure 2 both show a desktop using Internet Explorer 9 in two different resolutions. Figure 3 shows the same responsive site on a Windows Phone, also with Internet Explorer 9.
Figure 1 Navigation Appears on the Left
Figure 2 In an 800×600 Resized Window, Navigation Switches to the Top
Figure 3 The Same Site on a Windows Phone
If you’re looking for some great examples of responsive web design that take full advantage of media queries, the http://mediaqueri.es/ enthusiast site can be addictive, as Figure 4 shows.
Figure 4 A Collection of Sites That Use Media Queries
Media Query ListenersTaking media queries a step further, the CSS Object Model (CSSOM) working group at the W3C also created media query listeners, which provide an API for responding to media query changes. Instead of having to poll for changes or load several versions of a resource, you can use the API, for example, to download images only of a particular size when a media query match is triggered.
Today, Firefox and the Internet Explorer 10 Platform Preview implement media query listeners; you can see the demo “CSS3 Media Queries & Media Query Listeners” on the IE Test Drive.
A Word about the ViewportWhen testing media queries on mobile browsers, you might notice that the correct media queries are not actually being applied. When this happens, the mobile browser is doing some work on your behalf to render the page optimally on the smaller screen.
So do you think there isn’t a way of getting the real resolution? Actually there is, in the viewport meta tag. The viewport meta tag controls the logical dimensions and scaling of the mobile browser’s (chrome-less) window. Setting the width equal to the device-width works around the problem:
<meta name="viewport" content="width=device-width">
Other viewport settings include maximum-zoom and initial-scale.
A flexible grid-based layout is one of the cornerstones of responsive design. The term “grid” is used rather freely and doesn’t imply a requirement to implement any of the available grid frameworks. What it means here is using CSS for positioning and for laying out margins and spacing, and for implementing various web layout types in a new way. Layouts and text sizes are typically expressed in pixels. Designers love pixels. Photoshop loves pixels. But a pixel can be one dot on one device and eight dots on another. So how do you approach responsive web design if everything is pixel-based? You might not like the answer: You stop using pixel-based layouts and start using percentages or the em for sizing.
By basing text sizes, widths and margins on percentages or on the em, a unit of measurement based on a font’s point size, you can turn a fixed size into a relative size. This means you’ll need to do a little math to achieve a flexible grid and text size system. But the formula for calculating the em is very simple:
target ÷ context = result
Let’s say the normal context for the body font size is 16 pixels. If the designer specifies that the H1 should be 24 pixels, you can calculate the following:
24 ÷ 16 = 1.5
This results in the following CSS style:
Always take the context into account. Continuing with the previous example, if you have an element inside the H1 that needs to be 12 pixels, you use the current H1 as the context. The context is now 24 pixels, so the context calculation for “H1 a” is:
12 ÷ 24 = 0.5
And the CSS style is:
h1 a{ font-size: 0.5em; }
You can also use percentages. The calculation algorithm is the same; you just end up with percentages.
Flexible grids use this approach. You can find several frameworks to help you craft your grid, such as Fluid Grid System or Fluid 960 Grid System (a fluid version of 960 Grid System). Moreover, several groups within the W3C have submitted new specs for better flexible grids, with some useful results.
CSS3 Grid LayoutThe CSS3 Grid Layout (also known as Grid Alignment or, simply, the Grid), brings a typical grid system to CSS, similar to what XAML or Silverlight developers may be familiar with. At the time of this writing, the spec is an “Editor’s Draft.” It allows for defining regions in a layout, with columns and rows, spanning, spacing, padding, grid templates and more, enforcing full separation of concerns between HTML elements and CSS. Unlike HTML tables that are content, the Grid allows for placing HTML primitives into grid regions separate from actual content.
Combining the CSS3 Grid with media queries creates a powerful solution for building fluid, responsive applications.
How does the Grid work? You start by setting the display block to ‘grid’. (You need to use CSS vendor prefixes because this is not yet a CSS3 recommendation. Right now, only Internet Explorer 10 Platform Preview supports the spec, so you’ll see the CSS vendor prefix -ms- used here.) Let’s look at three examples of how you can set up different views depending on screen size. Media queries are used to apply different grid styles depending on the screen width.
In the first example, the HTML for defining the content consists of one header and three different blocks of text.
<div id="mygrid"> <header id="myheader"> <h1>Hello world</h1> </header> <div id="block1"> <h2>Lorem Ipsum section 1</h2> <p> Phasellus venenatis sem vel velit tincidunt tincidunt. Curabitur gravida, ante sit amet [... ...] </p> </div> <div id="block2"> <h2>Lorem Ipsum section 2</h2> <p> Nam tempus justo eu massa ultrices eget imperdiet ligula placerat. Suspendisse [... ...]. </p> </div> <div id="block3"> <h2>Lorem Ipsum section 3</h2> <ul> <li>Curabitur ultrices tristique purus, sed pellentesque magna scelerisque ut.</li> <li>[... ...] </li> </ul> </div> </div>You start by laying out the blocks of content under each other so that the content fits smartphones.
@media only screen and (max-width : 480px) { #mygrid { display: -ms-grid; margin: 3px; -ms-grid-columns: 100%; /*one column taking full width */ -ms-grid-rows: 70px auto auto auto; /*4 rows */ } #myheader { -ms-grid-row: 1; -ms-grid-column: 1; } #block1 { -ms-grid-row: 2; /*place into row 2 / column 1*/ -ms-grid-column: 1; } #block2 { -ms-grid-row: 3; -ms-grid-column: 1; } #block3 { -ms-grid-row: 4; -ms-grid-column: 1; } }
You can add background colors as shown in Figure 5 to make it clearer that you’re working with grid items.
Figure 5 Blocks of Content with Background Colors
In the second example, a media query applies styles defined for screen sizes greater than 481 pixels—anything wider than a typical smartphone. You can use the Grid to define two columns and move the blocks into desired positions, as below. The results are shown in Figure 6.
@media only screen and (min-width : 481px) { /*make two columns and move block 3 next to 1 — just because we can*/ #mygrid { display: -ms-grid; -ms-grid-columns: 10px 1fr 10px 1fr 10px; /*10px columns to spacing in between*/ -ms-grid-rows: 100px 1fr 1fr; /*100px row and two rows each taking 1 fraction of available space*/ margin: 5px; } #myheader { -ms-grid-row: 1; -ms-grid-column: 1; -ms-grid-column-span: 5; background-color: #EEB215; } #block1 { -ms-grid-row: 2; -ms-grid-column: 2; background-color: #B2B0B0; } #block2 { -ms-grid-row: 3; -ms-grid-column: 2; background-color: #726E6E; } #block3 { -ms-grid-row: 2; /*block 3 can go into row 2*/ -ms-grid-column: 4; background-color: #515050; } }
Figure 6 A New Layout with Two Adjacent Columns
The third grid sample displays on screen widths greater than 1220 pixels. You define a grid with a wide header that spans multiple columns and then define three columns, each occupying one fraction of the available space, with a few 10-pixel columns in between. The results are shown in Figure 7.
@media only screen and (min-width: 1220px) { #mygrid { display: -ms-grid; -ms-grid-columns: 1fr 10px 1fr 10px 1fr; -ms-grid-rows: 100px 1fr; margin: 5px; } #myheader { -ms-grid-row: 1; -ms-grid-column: 1; -ms-grid-column-span: 5; background-color: #EEB215; } #block1 { -ms-grid-row: 2; -ms-grid-column: 1; background-color: #B2B0B0; } #block2 { -ms-grid-row: 2; -ms-grid-column: 3; background-color: #726E6E; } #block3 { -ms-grid-row: 2; -ms-grid-column: 5; background-color: #515050; } }
Figure 7 Three Side-by-Side Columns with a Spanning Header
The Grid specification is a welcome addition for implementing responsive web designs.
Two other new CSS specifications are also worth mentioning: the Flexible Box Layout Module (Flexbox) and The Multi-column Layout Module. Both show a great deal of promise for designing responsive Websites.
Flexbox, currently a working draft at the W3C, adds support for four new layout modes: block, inline, table, and positioned. It enables you to lay out complex pages with relative position and constant size, even when screen sizes change.
The multi-column layout module is currently a candidate recommendation at the W3C. This solution is for content that you need to lay out in columns and that flow from one column into the next. You can view an interactive example of multi-column layout in this lab.
Flexible Images and MediaThe final aspect of responsive web design is flexible images and media. Basically, this feature allows you to adapt your images or other media to load differently depending on the device, either by scaling or by using the CSS overflow property.
Scaling in CSS is pretty simple to implement for both images and video. You can set the media element’s max-width to 100 percent, and the browser will make the image shrink and expand depending on its container. You should supply the image in the best quality and size possible and then let CSS adapt the image to the right size.
img, object { max-width: 100%; }
An alternative to scaling images is cropping them with CSS. For example, applying overflow:hidden allows you to crop images dynamically so that they fit into their containers as the containers resize to fit a new screen environment.
Having several options to scale and crop images in CSS might not be enough. Do you really need to take up all of a visitor’s mobile bandwidth because you don’t have a smaller version of an image? To better serve users, flexible images might mean using an alternative image—or even no image at all. Folks in the web design community are coming up with solutions based on JavaScript and cookies, and you can expect more of this as responsive web design evolves and becomes the basis for many quality Websites.
Legacy BrowsersWhat about older browsers that don’t support media queries? What about Internet Explorer before version 8, which has issues with scaling images? Solutions in the form of polyfills can help. Here are some useful examples.
- css3-mediaqueries.js by Wouter van der Graaf: code.google.com/p/css3-mediaqueries-js/
- Response.js: github.com/scottjehl/Respond
- Fluid images: unstoppablerobotninja.com/entry/fluid-images/
Jumping on the responsive web design wagon isn’t something to take lightly. Take into account what you need to achieve, and consider whether catering to a specific version of a desktop or mobile device makes the most sense.
Responsive web design is in its early stages. Web designers will continue to offer different opinions and recommend directions related to whether to build for mobile first, how to fit these decisions into the design process, whether to slice up the comps into all the different screen sizes, and so forth. And as more and more screen sizes and form factors arrive, the conversation will continue.
HTML and CSS standards are evolving to help web designers deal with these issues. It’s clear that some form of responsive web design will be used to meet the challenges, and it’s equally clear that standards will continue to evolve as better ways of handling the changing world of devices and browsers are discovered.
Here are some additional resources:
- Responsive Web Design
- Responsive Web Design Techniques, Tools and Design Strategies
- Hardboiled CSS3 Media Queries
- Golden Grid System
- A Brief Look at Grid-Based Layouts in Web Design
Transactional vs. Consultative Selling: Knowing the Difference Makes All the Difference
Unlike many “natural-born salespeople”, I never had the childhood epiphany of, after selling newspaper subscriptions door-to-door, gloriously realizing that I loved to sell things. I never imagined myself in a position that would require selling, much less that I’d be blogging about it and teaching others how to do it.
I learned to sell out of necessity; because if I didn’t, I wouldn’t be able to do what I truly loved—developing websites and helping clients market. Oh, and I wouldn’t make any money … did I mention that?
In the beginning, I felt a certain disdain for the word, because it didn’t seem to fit what I did when I met with clients. Yet, when things didn’t go as planned, I sensed that the missing ingredient had something to do with “selling”—or my lack of skill at it. Perhaps “selling your services” feels more comfortable, but make no mistake, it’s still “selling.”
Sales people are not needed to quote prices. They are the bridge between the selling price and the perception of value provided to earn the sale. – Jeffrey Gitomer
Perhaps it will help to define exactly what type of selling our industry requires, because there are two different types: transactional and consultative.
A Transactional sale is a simple, short-term sale in which the customer already knows what he needs, so little to no product knowledge is required on the sales side. Typically, these are product rather than service-based. Buying criteria usually hinges on price or ease of acquisition. Consultative selling is a more complex, long-term process involving collaboration of both buyer and seller, in which the latter must first develop an understanding of the customer’s business, industry, and needs, and then craft a solution to help the customer achieve their objectives. This is usually service or solution-based. The difference between the two can be easily understood from best-selling author Roy H. Williams’ comparison of the transactional vs. relational shopper:
The Transactional Shopper- Transactional shoppers are focused only on today’s transaction and give little thought to the possibility of future purchases.
- Their only fear is of paying more than they had to pay. Transactional shoppers are looking for price and value.
- They enjoy the process of comparing and negotiating and will likely shop at several stores before making their decision to purchase.
- Transactional shoppers do their own research so they won’t need the help of an expert. Consumer Reports are published primarily for the transactional shopper.
- Because they enjoy the process, transactional shoppers don’t consider their time spent shopping to be part of the purchase price.
- Anxious to share the “good deal” they’ve found, transactional shoppers are excellent sources of word-of-mouth advertising.
- Relational shoppers consider today’s transaction to be one in a long series of many future purchases. They are looking less for a product than for a store in which to buy it.
- Their only fear is of making a poor choice. Relational shoppers will purchase as soon as they have confidence. Will your store and your staff give them this confidence they seek?
- They don’t enjoy the process of shopping and negotiating.
- Relational shoppers are looking principally for an expert they can trust.
- They consider their time to be part of the purchase price.
- Confident that they have found “the right place to buy,” relational shoppers are very likely to become repeat customers.
The article goes on to say that, because some shoppers will be in transactional mode and others in relational mode, your success or failure hinges on knowing which and adjusting your selling style accordingly. In context, the article is talking to merchants and store owners, so his advice makes perfect sense. Some sales (like buying a cell phone) are not so black-and-white and end up being a mix of both transactional and consultative, depending on the buyer. Yet, in a purely consultative industry like ours, problems occurs when buyers attempt to engage our services using the transactional approach. These are people who won’t answer your questions, demand yousubmit a bid or want to know “how much?” without providing any information in return. Your success, however, lies in how you deal with them, and not—I repeat, not—in adjusting your selling style to match their buying mode.
Unless you enjoy being dictated to by demanding clients for whom “getting the lowest price” is the primary reason they buy.
The trick is to get your prospects to change their buying mode, rather than adjusting your selling style. Can a transactional buyer be converted into a relational one mid-sales stream? In my opinion, yes, but not all of the time. Knowing how to “flip the switch” is one sales skill I learned to master. But knowing how to deal with those who can’t (or won’t) be converted is another one entirely.
Continuing to deal with a prospect struck in transactional mode generally doesn’t turn out well, at least in my experience. What about you? Have you learned how to get transactional clients into a relational/consultative mode? Or do you default to switching your style to match theirs, then wonder why you lose the sale or end up cutting your price? Post your comments below.
SitePoint Podcast #147: The CSS Ninja with Ryan Seddon
Episode 147 of The SitePoint Podcast is now available! This week our regular interview host Louis Simoneau (@rssaddict) interviews Ryan Seddon (@ryanseddon) about his course on Modernizr, his work on fontdragr.com, and his other CSS projects too.
Listen in Your BrowserPlay this episode directly in your browser — just click the orange “play” button below:
Download this EpisodeYou can download this episode as a standalone MP3 file. Here’s the link:
- SitePoint Podcast #147: The CSS Ninja with Ryan Seddon (MP3, 26:05, 23.9MB)
The SitePoint Podcast is on iTunes! Add the SitePoint Podcast to your iTunes player. Or, if you don’t use iTunes, you can subscribe to the feed directly.
Episode SummaryLouis sits down with Bruce Lawson to talk about HTML5 semantics, usage, developed, packs, workarounds, polyfills and everything in between.
Browse the full list of links referenced in the show at http://delicious.com/sitepointpodcast/147.
Interview TranscriptTranscript to Follow.
Theme music by Mike Mella.
Thanks for listening! Feel free to let us know how we’re doing, or to continue the discussion, using the comments field below.
Managing Multiple Sites with WordPress Network
Here you are, a web developer currently working on several different WordPress sites at varying stages of development. They’re all in different directories on your domain so clients can have a look and see how they’re coming along. Out of nowhere, a major new WordPress version is released, and the Genesis framework you’re using to build all your sites needs to be updated.
Managing all your WordPress installations at a times like this can be a real hassle. This may not be your exact story, but I’m sure many of you can sympathize.
WordPress Network comes to the rescue in situations like this. Once you’ve set it up, you can put multiple sites in directories and subdomains with the click of a button. Since they all share the same WP installation you can update and edit them all in one fell swoop from your Network Admin dashboard.
Before you start, unless this is a brand new WordPress installation with nothing to lose, you’ll probably want to back up your database and files. You also need to disable any active plugins. You can start them back up after the network setup is complete.
Now for the nuts and bolts of creating a new WordPress Network.
First you’ll need to enable Multisite. Use your FTP client or web server’s file browser and download a file called wp-config.php and save it to your desktop or a folder. Open it with your text editor and add this line above where it says "/* That's all, stop editing! Happy blogging. */".
define('WP_ALLOW_MULTISITE', true);Save the file and upload it back to the server. Since the original wp-config.php is still there, it will ask you which file you want to keep. Choose “replace” or “overwrite” to save the newer, edited version.
You’ll need to refresh your admin panel in your browser to let these changes take effect.
In your Tools menu in the left sidebar, you’ll now see the Network Setup item. Click on that and then go to Create a Network of WordPress Sites. Follow the directions and choose a few options such as the URL structure and network name.
You’ll then be prompted to make a blogs.dir directory in /wp-content and it will provide several more lines of code to add to the wp-config.php file.
It will also tell you to add some rules to .htaccess, a server text file used to work with permissions and configurations for each directory.
Use your FTP program of choice to download this file. You may need to tell your FTP program not to hide this file, which you should be able to specify in the program’s options or preferences.
Even when you have downloaded the file, you may find your operating system’s file manager hides the file from you, so here are some directions for how to show it.
MacOpen Terminal, found in Applications/Utilities.
Type/paste in this and hit return/enter:
defaults write com.apple.finder AppleShowAllFiles -bool trueFinder must be restarted to allow this to take effect. In Terminal paste in this and hit enter:
killall FinderWhen you’re done, if you’d like to hide hidden files again, use the same line as used to hide it, but change “true” at the end to “false” and restart Finder.
WindowsPress the Start menu. Click on Control Panel, Appearance and Personalization, and then Folder Options. Go to the View tab. Under Advanced Settings, select “Show hidden files and folders” and hit “OK“.
Once you have shown hidden files, open .htaccess and add the code given by WordPress Network Setup, save the file and re-upload it.
It is possible that this file has not already been placed on your website. If, after setting your FTP program to display hidden files, you still don’t see the .htaccess file in your website’s root public directory, you’ll need to create a new one.
Create a new file in your text editor, paste in the code given by WordPress Network Setup, save that as .htaccess to your desktop or a folder, and upload it to the server, in the root public directory of your website.
When you log into the Network Admin, you’ll now see a “My Sites” item. Hover over that, and under “Network Admin”, there will be several administration items. When you click on “Sites”, you can add new sites with the click of a button!
Back under the main “My Sites” menu, you can manage all the network sites.
Setup is complete!
Now, with your WordPress network, updating and making changes to all your sites is simple and far less time-consuming.
What Expectations Are You Setting With Clients?
We’ve talked about nightmare projects and client red flags, but with today’s topic I’d like to turn the focus on us for a moment. Do you have demanding clients? Do you have clients that don’t pay on time? How about the client that emails, only to call five minutes later to make sure you received it, or the client that drops in to your office unannounced and wants to look over your shoulder while you make one last change to their website?
You’re probably thinking “but he said we were going to talk about us.”
I am.
It’s not your client’s fault they’re too demanding – it’s your fault for setting unrealistic expectations.
What expectations are you setting with clients?From the moment you first respond to a potential client you are setting expectations for how to communicate, how project flow works, and what they should expect from you. They are also setting expectations for what you can expect from them. It’s best to set the right expectations from the beginning – let’s look at a few ways: Single Point of Contact
One of the most frustrating issues for an account manager is a client that goes directly to other employees asking for changes or new work to be done. It circumvents the production schedule, rarely gets billed to the correct project (or even client), and causes confusion about priorities. The client should have a single point of contact – their account manager – and you should have a single point of contact with the client.
You also need to discuss how communication will be handled: email, phone, Basecamp? What is a reaonable response time? If you don’t set the expectation up front, they might expect a response to an email within an hour, when you might strive to respond within 1-2 business days.
Payment TermsLots of business owners hate talking about money, but it is a necessary evil. You’ve got to discuss and agree to the payment terms up front. Are the terms net 30, net 15, or immediately upon receipt? What happens if the client doesn’t pay on time? Are there penalties such as late fees or interest? Is there a possibility that their site could be taken down?
And on specific projects, if you require payment in phases is the payment due before work will begin on the next phase? What if a client fails to meet a deadline on a deliverable, is the payment still due?
It’s a lot to think about, but if you don’t think about it and discuss it with your client up front they’ll come to their own conclusions which may be very different.
Turnaround TimeTurnaround times are an area where clients often get the wrong expectations. It’s rarely discussed or put into writing, and we almost always try really hard to impress a client up front. So when you first begin working with a client, you bend over backwards to make them happy. You work nights, weekends, answer emails in five minutes, and do whatever it takes to turn the project around and make the client happy.
But as time goes on, you go back to a regular production mode, and the client still expects super-web-guy (or girl).
What is the typical turnaround time? Do you promise to start on new work immediately, within one or two business days, or does every project need to be fit into a production schedule? Even if you plan on dropping everything to work on a project for a new client, you need to discuss their overall expectations. If not, you’ll have clients calling at 4pm on Friday afternoon expecting you to drop everything to make five hours worth of changes.
Get a ContractWhether you work hourly or per project, a contract is a necessity. The contract shouldn’t just outline the technical details of the project or your hourly rate, it should include information about anything that can affect the project or your relationship. All of the topics above should be in your client agreement and per-project contracts.
How do you unset unrealistic expectations?Unless you’re just getting started and don’t have a single client, chances are you’ve already set some bad expectations. How do you reverse course without just ditching your clients and finding new ones?
My first suggestion is to slow down. Don’t answer emails immediately. Don’t always answer the phone. Always respond, and always return calls … but when you do, also try to set the expectation of proper communication in the form of an email signature or voicemail message. Something like “I check email and voicemail at 10 am and 3pm daily, and will get back to you witin one day.”
To start setting payment-related expectations, send a letter or email detailing changes to your process before they go into effect. If you plan to start charging a late fee or interest on past-due invoices, don’t wait until someone is past due to let them know.
No matter what the issue is with your client, a phone call or an in-person meeting can most likely straighten it out. If they aren’t paying on time, aren’t contacting the correct person, or seem to expect a level of service you don’t feel you can provide, just call them. Sit down and discuss it with them.
How do you go about setting expectations?How do you communicate to clients what they can expect from you?
Let us know in the comments below!
Tips for Designing an Amazing Proposal
We recently overhauled our proposal, and what we ended up with was pretty amazing. We started with a document that was all about us, from the design to the language in the proposal. When you picked it up, you knew who it was from immediately. But as we were working on a proposal for a very large project recently, we thought the proposal shouldn’t be as much about us as it is about the client.
So we started redesigning our proposals based on the client or project. We have always broken out our proposals into sections, and we decided to use large, full-color photographs to separate each section. We sprinkle additional color photoraphs throughout the proposal. The graphics relate to the client – not us. If the client is a real estate company, think homes and agents. If they are a restaurant, think gorgeous photos of food. You also should use their logo – it should be as big or bigger than your own.
We seriously reworded our proposal too. Instead of being bland copy about our agency that we simply copied and pasted into each new proposal, we tailored the copy to this specific project. We wrote about how our previous experience prepared us to do a great job on this project. We went into details about specific questions in their RFP by describing recent project we’d worked on.
We also went further than the RFP required. As we were researching the client and project, we had a lot of ideas that went above and beyond what they asked for, and we really wanted to make sure we included those in the proposal somehow. While we stuck to the required proposal structure in their RFP, we included additional suggestions in the sidebars and interstitial pages.
When was the last time you looked at your proposals? Is it time to give it a refresher?