├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── LICENSE ├── README-image.md ├── README.md ├── afontgarde.css ├── afontgarde.js ├── docs.css ├── fonts ├── icomoon.eot ├── icomoon.svg ├── icomoon.ttf ├── icomoon.woff └── png │ └── hamburger.png ├── lib ├── faceoff.js └── modernizr.fontface-generatedcontent.js ├── markup-image.html ├── markup-tests.html ├── markup.html ├── package.json └── src ├── afontgarde.tmpl.css ├── afontgarde.tmpl.js └── banner /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": true, 11 | "boss": true, 12 | "eqnull": true, 13 | "node": true 14 | } -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 2 | /*global module:false require*/ 3 | module.exports = function(grunt) { 4 | "use strict"; 5 | 6 | // Project configuration. 7 | grunt.initConfig({ 8 | // Metadata. 9 | pkg: grunt.file.readJSON('package.json'), 10 | banner: grunt.file.read( 'src/banner' ), 11 | jshint: { 12 | gruntfile: { 13 | options: { 14 | jshintrc: '.jshintrc' 15 | }, 16 | src: 'Gruntfile.js' 17 | }, 18 | src: { 19 | src: [ '<%= pkg.name %>.js' ] 20 | } 21 | }, 22 | watch: { 23 | gruntfile: { 24 | files: '<%= jshint.gruntfile.src %>', 25 | tasks: ['jshint:gruntfile'] 26 | }, 27 | src: { 28 | files: [ '<%= concat.js.src %>', '<%= concat.css.src %>' ], 29 | tasks: [ 'concat', 'replace' ] 30 | } 31 | }, 32 | bytesize: { 33 | src: { 34 | src: [ 35 | '<%= pkg.name %>.css', 36 | '<%= pkg.name %>.js' 37 | ] 38 | } 39 | }, 40 | concat: { 41 | options: { 42 | stripBanners: false, 43 | banner: '<%= banner %>' 44 | }, 45 | js: { 46 | src: [ 'node_modules/fontfaceonload/dist/fontfaceonload.js', 'src/<%= pkg.name %>.tmpl.js' ], 47 | dest: '<%= pkg.name %>.js' 48 | }, 49 | css: { 50 | src: [ 'src/<%= pkg.name %>.tmpl.css' ], 51 | dest: '<%= pkg.name %>.css' 52 | } 53 | }, 54 | 'gh-pages': { 55 | options: {}, 56 | src: ['.gitignore', '*.js', '*.css', '*.html', 'lib/*', 'fonts/**/*' ] 57 | }, 58 | replace: { 59 | dist: { 60 | options: { 61 | patterns: [ 62 | { 63 | match: /\{\{(\w*)\}\}/g, 64 | replacement: function( match, key ) { 65 | return grunt.template.process( "<%= pkg.config." + key + " %>" ); 66 | } 67 | } 68 | ] 69 | }, 70 | files: [ 71 | { 72 | expand: true, 73 | flatten: true, 74 | src: [ '<%= pkg.name %>.css', '<%= pkg.name %>.js' ], 75 | dest: './' 76 | } 77 | ] 78 | } 79 | } 80 | }); 81 | 82 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 83 | 84 | // Default task. 85 | grunt.registerTask('default', ['concat', 'replace', 'jshint', 'bytesize:src']); 86 | 87 | }; 88 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Filament Group 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README-image.md: -------------------------------------------------------------------------------- 1 | # Critical Icons 2 | 3 | ## Fallback to Image 4 | 5 | Warning: Try to use the other three a-font-garde use cases on the main README before moving forward with this use case. It is less reliable. 6 | 7 | ### [Demo](http://filamentgroup.github.io/a-font-garde/markup-image.html) 8 | 9 | 10 | Use a bitmap image like a PNG for better fallback compatibility. 11 | 12 | Requires a `@font-face` feature test like Modernizr that provides the `supports-fontface` class to operate correctly. 13 | 14 | * Modernizr 15 | * [Mat’s `face-off`](https://github.com/filamentgroup/face-off) 16 | * [Pixel Ambacht](http://pixelambacht.nl/2013/font-face-render-check/): Careful if you support IE8. This test requires an external request and thus may have a race condition for if you’re using background-image for a fallback. 17 | 18 | ### HTML for fallback to Bitmap 19 | 20 | 21 | 22 | 23 | Menu 24 | 25 | 26 | ### CSS for fallback to Bitmap 27 | 28 | .icon-fallback-img .icon-hamburger { 29 | /* Adjust to match the icon font size */ 30 | width: 1em; 31 | height: 1em; 32 | /* Note: BB5 doesn’t support background-images on pseudo-elements */ 33 | background: url("fonts/png/hamburger.png") no-repeat; 34 | } 35 | /* A-Grade */ 36 | .supports-fontface .icon-fallback-img .icon-hamburger:before { 37 | font-family: icomoon; 38 | content: "\e601"; 39 | } 40 | 41 | The fallback background-image is less reliable, since it does not check to make sure the icon font has successfully loaded. We do this so that the background-image request will not be prematurely triggered. If the HTTP request for the font fails, this will show the default Unicode character for `"\e601"`. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :warning: This project is archived and the repository is no longer maintained. 2 | 3 | # A Font Garde 4 | 5 | [![Filament Group](http://filamentgroup.com/images/fg-logo-positive-sm-crop.png) ](http://www.filamentgroup.com/) 6 | 7 | **A set of reliable (nay, bulletproof) patterns for icon fonts.** 8 | 9 | To start, you’ll probably want to **[read the Filament Group Blog Post](http://filamentgroup.com/lab/bulletproof_icon_fonts)**. 10 | 11 | Then, add `afontgarde.css` and `afontgarde.js` to your build to concat up into your web site. 12 | 13 | ## Uses Cases: 14 | 15 | 1. Critical Icon 16 | * Includes fallback text of varying length 17 | * Has size restrictions, fallback must have a similar size 18 | * Fallback to a [reliable Unicode equivalent](http://unicode.johnholtripley.co.uk/) (note: very few reliable cross-platform glyphs exist) 19 | * [Fallback to an image](README-image.md) (not recommended) 20 | 1. Icon as decoration, does not need a fallback but must not take up space (for proper centering/alignment of neighboring content) 21 | 22 | ### Support List 23 | 24 | 1. CSS including `:before`, `:after` pseudo-elements and `@font-face` 25 | 1. JS (`@font-face` loading test) 26 | 27 | ### [Demos](http://filamentgroup.github.io/a-font-garde/markup.html) 28 | 29 | ## Decorative Icons (No Fallback Required) 30 | 31 | ### HTML 32 | 33 | 34 | Share on Twitter (Sibling Text) 35 | 36 | Make sure you use sibling text here for labeling text—don’t nest the text inside of the icon `span`. We need `aria-hidden` on the icon to make sure it is not read aloud by screen readers. 37 | 38 | ### CSS 39 | 40 | .fontloaded .icon:before { 41 | font-family: icomoon; 42 | } 43 | .fontloaded .icon-twitter:before { 44 | content: "\e604"; 45 | } 46 | 47 | The `fontloaded` class is added by the FontFaceOnload script, which checks to make sure the Icomoon font has successfully loaded (just because a browser supports font-face doesn’t mean the request will succeed). 48 | 49 | ## Critical Icons with Fallback Text 50 | 51 | If the icon fails, the text must show. Otherwise hide it. 52 | 53 | ### HTML 54 | 55 | 56 | 57 | Fallback Text 58 | 59 | 60 | ### CSS 61 | 62 | *Reuse the decorative icon CSS above.* 63 | 64 | ## Critical Icons with Fallback Icons 65 | 66 | If the icon fails, a fallback icon is shown. Otherwise hide the default icon. 67 | 68 | ### HTML for fallback to Unicode Glyph 69 | 70 | 71 | 72 | Menu 73 | 74 | 75 | ### CSS for fallback to Unicode Glyph 76 | 77 | .icon-fallback-glyph .icon-hamburger:before { 78 | content: "\2261"; /* Hamburger */ 79 | /* Adjust to match the icon font size */ 80 | font-size: 2em; 81 | line-height: .5; 82 | } 83 | /* A-Grade */ 84 | .fontloaded .icon-fallback-glyph .icon-hamburger:before { 85 | content: "\e601"; 86 | } 87 | 88 | Choose your fallback glyph character with care. Cross-browser/platform compatibility may vary. Check John Holt Ripley’s [compatibility tables]( http://unicode.johnholtripley.co.uk/). 89 | 90 | ## JavaScript 91 | 92 | The JavaScript adds the appropriate classes to make sure that the font has loaded. 93 | 94 | ``` 95 | AFontGarde( 'icomoon', '\uE600\uE601\uE602\uE605' ); 96 | ``` 97 | 98 | ### Full options list 99 | ``` 100 | AFontGarde( 'icomoon', { 101 | glyphs: '\uE600\uE601\uE602\uE605', 102 | success: function() {}, 103 | error: function() {}, 104 | timeout: 10000 105 | }); 106 | ``` 107 | 108 | The first argument is the name of the `font-family`. The second argument is a few of the glyphs contained in the new font. We measure these characters to make sure the font has loaded successfully. 109 | 110 | ## Browser Support 111 | 112 | These browsers were tested, full browser support is more comprehensive: 113 | 114 | * Chrome 34 115 | * Firefox 29 116 | * Safari 7 117 | * iOS 6, iOS 7 Mobile Safari 118 | * Opera 12 119 | * Blackberry OS 7 120 | * Android 2.3 121 | * Internet Explorer 8, 9, 10, 11 122 | 123 | ### Fallback Experience 124 | 125 | * Opera Mini 126 | * Windows Phone 7.5 (Note: The icon-fallback-img method fails here because of a Modernizr false positive—an issue has been filed and resolved) 127 | * Opera 9 128 | * Blackberry OS 5 129 | * Blackberry OS 6 (technically supports SVG @font-face, but it’s horribly buggy. So we isolate the SVG entry to newer WebKit and opt into the fallback experience) 130 | * Internet Explorer 7 (The icon-fallback-glyph method falls back to text instead of an image due to a lack of :before/:after support. Requires additional Modernizr classes, noted below) 131 | 132 | ## Options 133 | 134 | ### Internet Explorer 7 and below 135 | 136 | To add support for Internet Explorer 7 and other browsers that don’t support `pseudo-elements` (`:before`, `:after`) use the Modernizr library to provide the pseudo-elements feature test for the `supports-generatedcontent` and `supports-no-generatedcontent` classes. 137 | 138 | You can configure Modernizr with the `supports-` classes prefix (make sure to include the `generatedcontent` test) or you can change the `supports-` prefix in a-font-garde. See the “Changing the CSS Prefix” section below for more information. 139 | 140 | ### Changing the `supports-` CSS Prefix 141 | 142 | To use a different CSS Prefix without editing the raw JS and CSS manually, you can optionally clone the repository and change the configuration setting in `package.json`. 143 | 144 | ``` 145 | "config": { 146 | "cssprefix": "supports-" 147 | } 148 | ``` 149 | 150 | Modify with your own CSS prefix and run `grunt` to generate new `afontgarde.css` and `afontgarde.js` files. 151 | -------------------------------------------------------------------------------- /afontgarde.css: -------------------------------------------------------------------------------- 1 | /*! afontgarde - v0.1.6 - 2015-03-13 2 | * https://github.com/filamentgroup/a-font-garde 3 | * Copyright (c) 2015 Filament Group c/o Zach Leatherman 4 | * MIT License */ 5 | 6 | .icon-fallback-text .icon { 7 | display: none; 8 | } 9 | /* 10 | ADDED BY afontgarde.js: 11 | Note: sure .FONT_NAME comes first for adjoining classes bug in IE7. 12 | 13 | .FONT_NAME.supports-generatedcontent .icon-fallback-text .icon { 14 | display: inline-block; 15 | }*/ 16 | 17 | .icon-fallback-img .text, 18 | .icon-fallback-glyph .text/*, 19 | ADDED BY afontgarde.js: 20 | Note: sure .FONT_NAME comes first for adjoining classes bug in IE7. 21 | 22 | .FONT_NAME.supports-generatedcontent .icon-fallback-text .text*/ { 23 | /* visually hide but accessible (h5bp.com) */ 24 | clip: rect(0 0 0 0); 25 | overflow: hidden; 26 | position: absolute; 27 | height: 1px; 28 | width: 1px; 29 | } 30 | 31 | /* Careful, don’t use adjoining classes here (IE7) */ 32 | .supports-no-generatedcontent .icon-fallback-glyph .text { 33 | clip: auto; 34 | overflow: visible; 35 | position: static; 36 | height: auto; 37 | width: auto; 38 | } 39 | /* 40 | ADDED BY afontgarde.js: 41 | .FONT_NAME .icon-fallback-glyph .icon:before { 42 | // inherit for font-size, line-height was not working on IE8 43 | font-size: 1em; 44 | font-size: inherit; 45 | line-height: 1; 46 | line-height: inherit; 47 | }*/ 48 | .icon-fallback-img .icon { 49 | display: inline-block; 50 | } 51 | .icon-fallback-img .icon:before { 52 | content: ""; 53 | } 54 | /* The img fallback version is not as reliable since it does not check to make sure the fontloaded font has loaded. If we did add the .fontloaded class, it would unnecessarily request the fallback image. */ 55 | .supports-fontface.supports-generatedcontent .icon-fallback-img .icon { 56 | background-image: none; 57 | } -------------------------------------------------------------------------------- /afontgarde.js: -------------------------------------------------------------------------------- 1 | /*! afontgarde - v0.1.6 - 2015-03-13 2 | * https://github.com/filamentgroup/a-font-garde 3 | * Copyright (c) 2015 Filament Group c/o Zach Leatherman 4 | * MIT License */ 5 | 6 | /*! fontfaceonload - v0.1.6 - 2015-03-13 7 | * https://github.com/zachleat/fontfaceonload 8 | * Copyright (c) 2015 Zach Leatherman (@zachleat) 9 | * MIT License */ 10 | 11 | ;(function( win, doc ) { 12 | "use strict"; 13 | 14 | var TEST_STRING = 'AxmTYklsjo190QW', 15 | SANS_SERIF_FONTS = 'sans-serif', 16 | SERIF_FONTS = 'serif', 17 | 18 | // lighter and bolder not supported 19 | weightLookup = { 20 | normal: '400', 21 | bold: '700' 22 | }, 23 | 24 | defaultOptions = { 25 | tolerance: 2, // px 26 | delay: 100, 27 | glyphs: '', 28 | success: function() {}, 29 | error: function() {}, 30 | timeout: 5000, 31 | weight: '400', // normal 32 | style: 'normal' 33 | }, 34 | 35 | // See https://github.com/typekit/webfontloader/blob/master/src/core/fontruler.js#L41 36 | style = [ 37 | 'display:block', 38 | 'position:absolute', 39 | 'top:-999px', 40 | 'left:-999px', 41 | 'font-size:48px', 42 | 'width:auto', 43 | 'height:auto', 44 | 'line-height:normal', 45 | 'margin:0', 46 | 'padding:0', 47 | 'font-variant:normal', 48 | 'white-space:nowrap' 49 | ], 50 | html = '
' + TEST_STRING + '
'; 51 | 52 | var FontFaceOnloadInstance = function() { 53 | this.fontFamily = ''; 54 | this.appended = false; 55 | this.serif = undefined; 56 | this.sansSerif = undefined; 57 | this.parent = undefined; 58 | this.options = {}; 59 | }; 60 | 61 | FontFaceOnloadInstance.prototype.getMeasurements = function () { 62 | return { 63 | sansSerif: { 64 | width: this.sansSerif.offsetWidth, 65 | height: this.sansSerif.offsetHeight 66 | }, 67 | serif: { 68 | width: this.serif.offsetWidth, 69 | height: this.serif.offsetHeight 70 | } 71 | }; 72 | }; 73 | 74 | FontFaceOnloadInstance.prototype.load = function () { 75 | var startTime = new Date(), 76 | that = this, 77 | serif = that.serif, 78 | sansSerif = that.sansSerif, 79 | parent = that.parent, 80 | appended = that.appended, 81 | dimensions, 82 | options = this.options, 83 | ref = options.reference; 84 | 85 | function getStyle( family ) { 86 | return style 87 | .concat( [ 'font-weight:' + options.weight, 'font-style:' + options.style ] ) 88 | .concat( "font-family:" + family ) 89 | .join( ";" ); 90 | } 91 | 92 | var sansSerifHtml = html.replace( /\%s/, getStyle( SANS_SERIF_FONTS ) ), 93 | serifHtml = html.replace( /\%s/, getStyle( SERIF_FONTS ) ); 94 | 95 | if( !parent ) { 96 | parent = that.parent = doc.createElement( "div" ); 97 | } 98 | 99 | parent.innerHTML = sansSerifHtml + serifHtml; 100 | sansSerif = that.sansSerif = parent.firstChild; 101 | serif = that.serif = sansSerif.nextSibling; 102 | 103 | if( options.glyphs ) { 104 | sansSerif.innerHTML += options.glyphs; 105 | serif.innerHTML += options.glyphs; 106 | } 107 | 108 | function hasNewDimensions( dims, el, tolerance ) { 109 | return Math.abs( dims.width - el.offsetWidth ) > tolerance || 110 | Math.abs( dims.height - el.offsetHeight ) > tolerance; 111 | } 112 | 113 | function isTimeout() { 114 | return ( new Date() ).getTime() - startTime.getTime() > options.timeout; 115 | } 116 | 117 | (function checkDimensions() { 118 | if( !ref ) { 119 | ref = doc.body; 120 | } 121 | if( !appended && ref ) { 122 | ref.appendChild( parent ); 123 | appended = that.appended = true; 124 | 125 | dimensions = that.getMeasurements(); 126 | 127 | // Make sure we set the new font-family after we take our initial dimensions: 128 | // handles the case where FontFaceOnload is called after the font has already 129 | // loaded. 130 | sansSerif.style.fontFamily = that.fontFamily + ', ' + SANS_SERIF_FONTS; 131 | serif.style.fontFamily = that.fontFamily + ', ' + SERIF_FONTS; 132 | } 133 | 134 | if( appended && dimensions && 135 | ( hasNewDimensions( dimensions.sansSerif, sansSerif, options.tolerance ) || 136 | hasNewDimensions( dimensions.serif, serif, options.tolerance ) ) ) { 137 | 138 | options.success(); 139 | } else if( isTimeout() ) { 140 | options.error(); 141 | } else { 142 | if( !appended && "requestAnimationFrame" in window ) { 143 | win.requestAnimationFrame( checkDimensions ); 144 | } else { 145 | win.setTimeout( checkDimensions, options.delay ); 146 | } 147 | } 148 | })(); 149 | }; // end load() 150 | 151 | FontFaceOnloadInstance.prototype.checkFontFaces = function( timeout ) { 152 | var _t = this; 153 | doc.fonts.forEach(function( font ) { 154 | if( font.family.toLowerCase() === _t.fontFamily.toLowerCase() && 155 | ( weightLookup[ font.weight ] || font.weight ) === ''+_t.options.weight && 156 | font.style === _t.options.style ) { 157 | font.load().then(function() { 158 | _t.options.success(); 159 | win.clearTimeout( timeout ); 160 | }); 161 | } 162 | }); 163 | }; 164 | 165 | FontFaceOnloadInstance.prototype.init = function( fontFamily, options ) { 166 | var timeout; 167 | 168 | for( var j in defaultOptions ) { 169 | if( !options.hasOwnProperty( j ) ) { 170 | options[ j ] = defaultOptions[ j ]; 171 | } 172 | } 173 | 174 | this.options = options; 175 | this.fontFamily = fontFamily; 176 | 177 | // For some reason this was failing on afontgarde + icon fonts. 178 | if( !options.glyphs && "fonts" in doc ) { 179 | if( options.timeout ) { 180 | timeout = win.setTimeout(function() { 181 | options.error(); 182 | }, options.timeout ); 183 | } 184 | 185 | this.checkFontFaces( timeout ); 186 | } else { 187 | this.load(); 188 | } 189 | }; 190 | 191 | var FontFaceOnload = function( fontFamily, options ) { 192 | var instance = new FontFaceOnloadInstance(); 193 | instance.init(fontFamily, options); 194 | 195 | return instance; 196 | }; 197 | 198 | // intentional global 199 | win.FontFaceOnload = FontFaceOnload; 200 | })( this, this.document ); 201 | 202 | /* 203 | * A Font Garde 204 | */ 205 | 206 | ;(function( w ) { 207 | 208 | var doc = w.document, 209 | ref, 210 | css = ['.FONT_NAME.supports-generatedcontent .icon-fallback-text .icon { display: inline-block; }', 211 | '.FONT_NAME.supports-generatedcontent .icon-fallback-text .text { clip: rect(0 0 0 0); overflow: hidden; position: absolute; height: 1px; width: 1px; }', 212 | '.FONT_NAME .icon-fallback-glyph .icon:before { font-size: 1em; font-size: inherit; line-height: 1; line-height: inherit; }']; 213 | 214 | function addEvent( type, callback ) { 215 | if( 'addEventListener' in w ) { 216 | return w.addEventListener( type, callback, false ); 217 | } else if( 'attachEvent' in w ) { 218 | return w.attachEvent( 'on' + type, callback ); 219 | } 220 | } 221 | 222 | // options can be a string of glyphs or an options object to pass into FontFaceOnload 223 | AFontGarde = function( fontFamily, options ) { 224 | var fontFamilyClassName = fontFamily.toLowerCase().replace( /\s/g, '' ), 225 | executed = false; 226 | 227 | function init() { 228 | if( executed ) { 229 | return; 230 | } 231 | executed = true; 232 | 233 | if( typeof FontFaceOnload === 'undefined' ) { 234 | throw 'FontFaceOnload is a prerequisite.'; 235 | } 236 | 237 | if( !ref ) { 238 | ref = doc.getElementsByTagName( 'script' )[ 0 ]; 239 | } 240 | var style = doc.createElement( 'style' ), 241 | cssContent = css.join( '\n' ).replace( /FONT_NAME/gi, fontFamilyClassName ); 242 | 243 | style.setAttribute( 'type', 'text/css' ); 244 | if( style.styleSheet ) { 245 | style.styleSheet.cssText = cssContent; 246 | } else { 247 | style.appendChild( doc.createTextNode( cssContent ) ); 248 | } 249 | ref.parentNode.insertBefore( style, ref ); 250 | 251 | var opts = { 252 | timeout: 5000, 253 | success: function() { 254 | // If you’re using more than one icon font, change this classname (and in a-font-garde.css) 255 | doc.documentElement.className += ' ' + fontFamilyClassName; 256 | 257 | if( options && options.success ) { 258 | options.success(); 259 | } 260 | } 261 | }; 262 | 263 | // These characters are a few of the glyphs from the font above */ 264 | if( typeof options === "string" ) { 265 | opts.glyphs = options; 266 | } else { 267 | for( var j in options ) { 268 | if( options.hasOwnProperty( j ) && j !== "success" ) { 269 | opts[ j ] = options[ j ]; 270 | } 271 | } 272 | } 273 | 274 | FontFaceOnload( fontFamily, opts ); 275 | } 276 | 277 | // MIT credit: filamentgroup/shoestring 278 | addEvent( "DOMContentLoaded", init ); 279 | addEvent( "readystatechange", init ); 280 | addEvent( "load", init ); 281 | 282 | if( doc.readyState === "complete" ){ 283 | init(); 284 | } 285 | }; 286 | 287 | })( this ); -------------------------------------------------------------------------------- /docs.css: -------------------------------------------------------------------------------- 1 | /* Logo */ 2 | .header { 3 | background: #247201 url(http://filamentgroup.com/images/headerbg-new.jpg) no-repeat bottom left; 4 | } 5 | #fg-logo { 6 | text-indent: -9999px; 7 | margin: 0 auto; 8 | width: 287px; 9 | height: 52px; 10 | background-image: url(http://filamentgroup.com/images/fg-logo-icon.png); 11 | } 12 | @media (-webkit-min-device-pixel-ratio: 1.5), (min-device-pixel-ratio: 1.5){ 13 | #fg-logo { 14 | background-size: 287px 52px; 15 | background-image: url(http://filamentgroup.com/images/fg-logo-icon-lrg.png); 16 | } 17 | } 18 | /* Demo styles */ 19 | body { 20 | font-family: sans-serif; 21 | font-size: 100%; 22 | } 23 | .docs-main { 24 | margin: 1em auto; 25 | max-width: 46em; 26 | } 27 | label { 28 | display: block; 29 | margin: 1em 0; 30 | } 31 | input, 32 | textarea { 33 | display: block; 34 | width: 100%; 35 | -webkit-box-sizing: border-box; 36 | -moz-box-sizing: border-box; 37 | box-sizing: border-box; 38 | 39 | margin-top: .4em; 40 | padding: .6em; 41 | font-size: 100%; 42 | } 43 | 44 | .menu { 45 | background-color: white; 46 | box-sizing: border-box; 47 | border: 1px solid black; 48 | width: 10em; 49 | } 50 | 51 | .menu ul, .menu ol { 52 | list-style: none; 53 | padding: 5px; 54 | margin: 0; 55 | } 56 | 57 | .menu-selected { 58 | color: white; 59 | background-color: #aaa; 60 | } 61 | 62 | input { 63 | box-sizing: border-box; 64 | width: 10em; 65 | } 66 | 67 | h1.docs, 68 | h2.docs, 69 | h3.docs, 70 | h4.docs, 71 | h5.docs { 72 | font-weight: 500; 73 | margin: 1em 0; 74 | text-transform: none; 75 | color: #000; 76 | clear: both; 77 | } 78 | 79 | h1.docs { font-size: 2.8em; margin-top: .1em; text-transform: uppercase; } 80 | h2.docs { font-size: 2.2em; margin-top: 1.5em; border-top:1px solid #ddd; padding-top: .6em; float:none; } 81 | h3.docs { font-size: 1.6em; margin-top: 1.5em; margin-bottom: .5em; } 82 | h4.docs { font-size: 1.4em; margin-top: 1.5em; } 83 | 84 | p.docs, 85 | p.docs-intro, 86 | ol.docs, 87 | ul.docs, 88 | p.docs-note, 89 | dl.docs { 90 | margin: 1em 0; 91 | font-size: 1em; 92 | } 93 | 94 | ul.docs, 95 | ol.docs { 96 | padding-bottom: .5em; 97 | } 98 | ol.docs li, 99 | ul.docs li { 100 | margin-bottom: 8px; 101 | } 102 | ul.docs ul, 103 | ol.docs ul { 104 | padding-top: 8px; 105 | } 106 | .docs code { 107 | font-size: 1.1em; 108 | } 109 | 110 | p.docs strong { 111 | font-weight: bold; 112 | } 113 | 114 | .docs-note { 115 | background-color: #FFFAA4; 116 | } 117 | .docs-note p, 118 | .docs-note pre, 119 | p.docs-note { 120 | padding: .5em; 121 | margin: 0; 122 | } 123 | 124 | 125 | /** 126 | * prism.js default theme for JavaScript, CSS and HTML 127 | * Based on dabblet (http://dabblet.com) 128 | * @author Lea Verou 129 | */ 130 | 131 | code[class*="language-"], 132 | pre[class*="language-"] { 133 | color: black; 134 | text-shadow: 0 1px white; 135 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 136 | direction: ltr; 137 | text-align: left; 138 | white-space: pre; 139 | word-spacing: normal; 140 | font-size: 0.8em; 141 | 142 | -moz-tab-size: 4; 143 | -o-tab-size: 4; 144 | tab-size: 4; 145 | 146 | -webkit-hyphens: none; 147 | -moz-hyphens: none; 148 | -ms-hyphens: none; 149 | hyphens: none; 150 | } 151 | 152 | @media print { 153 | code[class*="language-"], 154 | pre[class*="language-"] { 155 | text-shadow: none; 156 | } 157 | } 158 | 159 | /* Code blocks */ 160 | pre[class*="language-"] { 161 | padding: 1em; 162 | margin: .5em 0; 163 | overflow: auto; 164 | } 165 | 166 | :not(pre) > code[class*="language-"], 167 | pre[class*="language-"] { 168 | background: #f5f2f0; 169 | } 170 | 171 | /* Inline code */ 172 | :not(pre) > code[class*="language-"] { 173 | padding: .1em; 174 | border-radius: .3em; 175 | } 176 | 177 | pre[class*="language-"] { 178 | padding: 1em; 179 | margin: 0; 180 | margin-bottom: 1em; 181 | } 182 | 183 | -------------------------------------------------------------------------------- /fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/a-font-garde/8f324db7b1a35d360b7fb115c023449dcf3c988e/fonts/icomoon.eot -------------------------------------------------------------------------------- /fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/a-font-garde/8f324db7b1a35d360b7fb115c023449dcf3c988e/fonts/icomoon.ttf -------------------------------------------------------------------------------- /fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/a-font-garde/8f324db7b1a35d360b7fb115c023449dcf3c988e/fonts/icomoon.woff -------------------------------------------------------------------------------- /fonts/png/hamburger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/a-font-garde/8f324db7b1a35d360b7fb115c023449dcf3c988e/fonts/png/hamburger.png -------------------------------------------------------------------------------- /lib/faceoff.js: -------------------------------------------------------------------------------- 1 | (function( win, undefined ) { 2 | "use strict"; 3 | 4 | var doc = document, 5 | head = doc.head || doc.getElementsByTagName( "head" )[ 0 ] || doc.documentElement, 6 | style = doc.createElement( "style" ), 7 | rule = "@font-face { font-family: 'webfont'; src: 'https://'; }", 8 | res = false, 9 | blacklist = (function() { 10 | var ua = win.navigator.userAgent.toLowerCase(), 11 | wkvers = ua.match( /applewebkit\/([0-9]+)/gi ) && parseFloat( RegExp.$1 ), 12 | webos = ua.match( /w(eb)?osbrowser/gi ), 13 | wppre8 = ua.indexOf( "windows phone" ) > -1 && win.navigator.userAgent.match( /IEMobile\/([0-9])+/ ) && parseFloat( RegExp.$1 ) >= 9, 14 | oldandroid = wkvers < 533 && ua.indexOf( "Android 2.1" ) > -1; 15 | 16 | return webos || oldandroid || wppre8; 17 | }()), 18 | sheet; 19 | 20 | style.type = "text/css"; 21 | head.insertBefore( style, head.firstChild ); 22 | sheet = style.sheet || style.styleSheet; 23 | 24 | if ( !!sheet && !blacklist ) { 25 | try { 26 | sheet.insertRule( rule, 0 ); 27 | res = sheet.cssRules[ 0 ].cssText && ( /webfont/i ).test( sheet.cssRules[ 0 ].cssText ); 28 | sheet.deleteRule( sheet.cssRules.length - 1 ); 29 | } catch( e ) { } 30 | } 31 | if( res ) { 32 | var html = document.getElementsByTagName( "html" )[ 0 ]; 33 | html.setAttribute( "class", ( html.getAttribute( "class" ) ? html.getAttribute( "class" ) + " " : "" ) + "supports-fontface" ); 34 | } 35 | }( this )); -------------------------------------------------------------------------------- /lib/modernizr.fontface-generatedcontent.js: -------------------------------------------------------------------------------- 1 | /* Modernizr 2.7.1 (Custom Build) | MIT & BSD 2 | * Build: http://modernizr.com/download/#-fontface-generatedcontent-cssclasses-teststyles-cssclassprefix:supports! 3 | */ 4 | ;window.Modernizr=function(a,b,c){function w(a){j.cssText=a}function x(a,b){return w(prefixes.join(a+";")+(b||""))}function y(a,b){return typeof a===b}function z(a,b){return!!~(""+a).indexOf(b)}function A(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:y(f,"function")?f.bind(d||b):f}return!1}var d="2.7.1",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l=":)",m={}.toString,n={},o={},p={},q=[],r=q.slice,s,t=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["­",'"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},u={}.hasOwnProperty,v;!y(u,"undefined")&&!y(u.call,"undefined")?v=function(a,b){return u.call(a,b)}:v=function(a,b){return b in a&&y(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=r.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(r.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(r.call(arguments)))};return e}),n.fontface=function(){var a;return t('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},n.generatedcontent=function(){var a;return t(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a};for(var B in n)v(n,B)&&(s=B.toLowerCase(),e[s]=n[B](),q.push((e[s]?"":"no-")+s));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)v(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" supports-"+(b?"":"no-")+a),e[a]=b}return e},w(""),i=k=null,e._version=d,e.testStyles=t,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" supports-js supports-"+q.join(" supports-"):""),e}(this,this.document); -------------------------------------------------------------------------------- /markup-image.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Icon Fonts 7 | 8 | 39 | 40 | 41 |

Fallback to Image

42 |

Fixed colors, must know the size of the glyph, requires `background-size` to resize.

43 | 44 | 45 | Menu 46 | 47 | 48 |

HTML

49 |

50 | <span class="icon-fallback-img">
51 | 	<span class="icon icon-hamburger" aria-hidden="true"></span>
52 | 	<span class="text">Menu</span>
53 | </span>
54 | 	
55 | 56 |

Failed experiments:

57 | 60 | 61 | 62 | 63 | 66 | 67 | -------------------------------------------------------------------------------- /markup-tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Icon Fonts 7 | 8 | 44 | 45 | 46 |

Bad Examples (For Testing Only)

47 |

Decorative Icon without Fallback (Nested Text)

48 |

If you nest the text, the icon may confuse screen readers (unable to use aria-hidden).

49 | Share on Twitter (Nested Text) 50 | 51 | 52 | 55 | 56 | -------------------------------------------------------------------------------- /markup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Icon Fonts 7 | 8 | 9 | 10 | 71 | 72 | 73 |
74 |
75 | 76 |
77 |
78 |

Demo of A Font Garde 79 | A variety of test cases and tools for safe font-icon usage. 80 |

81 | 86 |
87 |
88 | 89 |
90 |

Use Case #1: Critical Icons with Fallback Text

91 |

Doesn’t show supplementary text, but must show fallback text without the icon. Supplementary text is still available to screen readers.

92 | 93 | 94 | 95 | 96 | Twitter 97 | 98 | 99 | 100 | 101 | Facebook 102 | 103 | 104 | 105 | 106 | Google Plus 107 | 108 | 109 | 110 | 111 | RSS Feed 112 | 113 | 114 |

HTML

115 |

116 | <span class="icon-fallback-text">
117 | 	<!-- requires an element for aria-hidden -->
118 | 	<span class="icon icon-twitter" aria-hidden="true"></span>
119 | 	<span class="text">Twitter</span>
120 | </span>
121 | 		
122 | 123 |

Failed experiments:

124 | 127 | 128 |

Use Case #2: Critical Icons with Fallback Icon

129 |

Doesn’t show supplementary text, must show fallback icon without the primary icon. This can be a background image or a Unicode glyph. Supplementary text is still available to screen readers.

130 | 131 |

Fallback to Glyph

132 | 133 | 134 | Menu 135 | 136 | 137 |

Warning: In browsers that don’t support :before/:after (IE6-7), fallback is text.

138 | 139 |

HTML

140 |

141 | <span class="icon-fallback-glyph">
142 | 	<span class="icon icon-hamburger" aria-hidden="true"></span>
143 | 	<span class="text">Menu</span>
144 | </span>
145 | 	
146 | 147 |

Use Case #3: Decorative Icon without Fallback

148 |

Shows supplementary text, does not need fallback experience without the icon.

149 | 150 | 151 | Share on Twitter (Sibling Text) 152 | 153 |

HTML

154 |

155 | <span class="icon icon-twitter" aria-hidden="true"></span>
156 | Share on Twitter (Sibling Text)
157 | 	
158 |
159 | 160 | 161 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "afontgarde", 3 | "version": "0.1.6", 4 | "description": "The safest way to use font icons.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/filamentgroup/a-font-garde.git" 8 | }, 9 | "author": "Filament Group c/o Zach Leatherman", 10 | "license": "MIT", 11 | "bugs": { 12 | "url": "https://github.com/filamentgroup/a-font-garde/issues" 13 | }, 14 | "homepage": "https://github.com/filamentgroup/a-font-garde", 15 | "devDependencies": { 16 | "grunt": "~0.4.5", 17 | "grunt-bytesize": "~0.1.1", 18 | "grunt-contrib-concat": "~0.5.1", 19 | "grunt-contrib-jshint": "~0.11.0", 20 | "grunt-contrib-watch": "~0.6.1", 21 | "grunt-gh-pages": "~0.10.0", 22 | "grunt-replace": "~0.8.0", 23 | "matchdep": "~0.3.0" 24 | }, 25 | "config": { 26 | "cssprefix": "supports-", 27 | "iconclass": "icon" 28 | }, 29 | "dependencies": { 30 | "fontfaceonload": "^0.1.6" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/afontgarde.tmpl.css: -------------------------------------------------------------------------------- 1 | .icon-fallback-text .icon { 2 | display: none; 3 | } 4 | /* 5 | ADDED BY afontgarde.js: 6 | Note: sure .FONT_NAME comes first for adjoining classes bug in IE7. 7 | 8 | .FONT_NAME.{{cssprefix}}generatedcontent .icon-fallback-text .icon { 9 | display: inline-block; 10 | }*/ 11 | 12 | .icon-fallback-img .text, 13 | .icon-fallback-glyph .text/*, 14 | ADDED BY afontgarde.js: 15 | Note: sure .FONT_NAME comes first for adjoining classes bug in IE7. 16 | 17 | .FONT_NAME.{{cssprefix}}generatedcontent .icon-fallback-text .text*/ { 18 | /* visually hide but accessible (h5bp.com) */ 19 | clip: rect(0 0 0 0); 20 | overflow: hidden; 21 | position: absolute; 22 | height: 1px; 23 | width: 1px; 24 | } 25 | 26 | /* Careful, don’t use adjoining classes here (IE7) */ 27 | .{{cssprefix}}no-generatedcontent .icon-fallback-glyph .text { 28 | clip: auto; 29 | overflow: visible; 30 | position: static; 31 | height: auto; 32 | width: auto; 33 | } 34 | /* 35 | ADDED BY afontgarde.js: 36 | .FONT_NAME .icon-fallback-glyph .icon:before { 37 | // inherit for font-size, line-height was not working on IE8 38 | font-size: 1em; 39 | font-size: inherit; 40 | line-height: 1; 41 | line-height: inherit; 42 | }*/ 43 | .icon-fallback-img .{{iconclass}} { 44 | display: inline-block; 45 | } 46 | .icon-fallback-img .{{iconclass}}:before { 47 | content: ""; 48 | } 49 | /* The img fallback version is not as reliable since it does not check to make sure the fontloaded font has loaded. If we did add the .fontloaded class, it would unnecessarily request the fallback image. */ 50 | .{{cssprefix}}fontface.{{cssprefix}}generatedcontent .icon-fallback-img .{{iconclass}} { 51 | background-image: none; 52 | } -------------------------------------------------------------------------------- /src/afontgarde.tmpl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A Font Garde 3 | */ 4 | 5 | ;(function( w ) { 6 | 7 | var doc = w.document, 8 | ref, 9 | css = ['.FONT_NAME.{{cssprefix}}generatedcontent .icon-fallback-text .icon { display: inline-block; }', 10 | '.FONT_NAME.{{cssprefix}}generatedcontent .icon-fallback-text .text { clip: rect(0 0 0 0); overflow: hidden; position: absolute; height: 1px; width: 1px; }', 11 | '.FONT_NAME .icon-fallback-glyph .icon:before { font-size: 1em; font-size: inherit; line-height: 1; line-height: inherit; }']; 12 | 13 | function addEvent( type, callback ) { 14 | if( 'addEventListener' in w ) { 15 | return w.addEventListener( type, callback, false ); 16 | } else if( 'attachEvent' in w ) { 17 | return w.attachEvent( 'on' + type, callback ); 18 | } 19 | } 20 | 21 | // options can be a string of glyphs or an options object to pass into FontFaceOnload 22 | AFontGarde = function( fontFamily, options ) { 23 | var fontFamilyClassName = fontFamily.toLowerCase().replace( /\s/g, '' ), 24 | executed = false; 25 | 26 | function init() { 27 | if( executed ) { 28 | return; 29 | } 30 | executed = true; 31 | 32 | if( typeof FontFaceOnload === 'undefined' ) { 33 | throw 'FontFaceOnload is a prerequisite.'; 34 | } 35 | 36 | if( !ref ) { 37 | ref = doc.getElementsByTagName( 'script' )[ 0 ]; 38 | } 39 | var style = doc.createElement( 'style' ), 40 | cssContent = css.join( '\n' ).replace( /FONT_NAME/gi, fontFamilyClassName ); 41 | 42 | style.setAttribute( 'type', 'text/css' ); 43 | if( style.styleSheet ) { 44 | style.styleSheet.cssText = cssContent; 45 | } else { 46 | style.appendChild( doc.createTextNode( cssContent ) ); 47 | } 48 | ref.parentNode.insertBefore( style, ref ); 49 | 50 | var opts = { 51 | timeout: 5000, 52 | success: function() { 53 | // If you’re using more than one icon font, change this classname (and in a-font-garde.css) 54 | doc.documentElement.className += ' ' + fontFamilyClassName; 55 | 56 | if( options && options.success ) { 57 | options.success(); 58 | } 59 | } 60 | }; 61 | 62 | // These characters are a few of the glyphs from the font above */ 63 | if( typeof options === "string" ) { 64 | opts.glyphs = options; 65 | } else { 66 | for( var j in options ) { 67 | if( options.hasOwnProperty( j ) && j !== "success" ) { 68 | opts[ j ] = options[ j ]; 69 | } 70 | } 71 | } 72 | 73 | FontFaceOnload( fontFamily, opts ); 74 | } 75 | 76 | // MIT credit: filamentgroup/shoestring 77 | addEvent( "DOMContentLoaded", init ); 78 | addEvent( "readystatechange", init ); 79 | addEvent( "load", init ); 80 | 81 | if( doc.readyState === "complete" ){ 82 | init(); 83 | } 84 | }; 85 | 86 | })( this ); -------------------------------------------------------------------------------- /src/banner: -------------------------------------------------------------------------------- 1 | /*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> 2 | <%= pkg.homepage ? " * " + pkg.homepage : "" %> 3 | * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %> 4 | * <%= pkg.license %> License */ 5 | 6 | --------------------------------------------------------------------------------