├── .bowerrc ├── .eslintrc ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE.txt ├── README.md ├── _unified_manifest.json ├── bower.json ├── dist ├── jquery.awesome-cursor.js └── jquery.awesome-cursor.min.js ├── karma.conf.js ├── package.json ├── src ├── .jshintrc └── jquery.awesome-cursor.js └── test ├── .jshintrc ├── awesome-cursor-test-font ├── fonts │ ├── AwesomeCursorTest.eot │ ├── AwesomeCursorTest.svg │ ├── AwesomeCursorTest.ttf │ └── AwesomeCursorTest.woff └── style.css ├── expected ├── awesome-cursor-test-font │ ├── black-pencil-36.png │ └── green-brush-30-effects.png ├── black-desktop-22.png ├── black-globe-32.png ├── black-globe-flip-b-32.png ├── black-outline-paint-brush-32.png ├── black-outline-paint-brush-flip-both-32.png ├── black-outline-paint-brush-flip-horizontal-32.png ├── black-outline-paint-brush-flip-vertical-32.png ├── black-pencil-rotate45.png ├── blue-outline-paint-brush-rotate45-flip-h.png ├── blue-outline-paint-brush-rotate45.png ├── green-pencil-rotate45-flip-b.png ├── green-pencil-rotate45-flip-h.png ├── lime-flag-18.png ├── lime-flag-flip-v-18.png ├── pink-usb-32.png ├── red-pencil-18.png ├── red-pencil-flip-h-18.png └── red-wrench-rotate-45.png ├── jquery-awesome-cursor.html └── jquery-awesome-cursor_test.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "browser": true 5 | }, 6 | "globals": { 7 | "jQuery": true, 8 | "$": true, 9 | "require": true, 10 | "define": true 11 | }, 12 | "rules": { 13 | "no-extra-semi": 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /bower_components/ 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": "vars", 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "maxlen": 100 22 | } 23 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | src 3 | bower_components 4 | _unified_manifest.json 5 | bower.json 6 | karma.conf.js 7 | Gruntfile.js 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | install: 3 | - npm install -g grunt-cli 4 | - npm install 5 | - bower install 6 | before_script: 7 | - export DISPLAY=:99.0 8 | - sh -e /etc/init.d/xvfb start 9 | node_js: 10 | - '5' 11 | - '4' 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Getting started 4 | 5 | - Install all of the necessary dependencies for development, by running `npm install && bower install` 6 | - Check that the tests pass by running `grunt test` 7 | - You will notice that some of the tests get skipped because they are marked as browser-only. This is because there are some issues with running the tests from PhantomJS (one such problem is that it doesn't report 8 | the cursor hotspot position when getting the value of the `cursor` style). To run these tests, open the test page in a browser (Chrome users: note that you will have to host the test directory locally due to restrictions 9 | on accessing file:// URLs in Chrome); 10 | 11 | ## Important notes 12 | - Please don't edit files in the `dist` subdirectory as they are generated via Grunt. You'll find source code in the `src` subdirectory! 13 | - Please don't edit `bower.json`, `awesome-cursor.jquery.json` or `package.json` directly, as they are also generated via Grunt. Instead, make your changes in `_unified_manifest.json`. 14 | 15 | ### Code style 16 | Regarding code style like indentation and whitespace, **follow the conventions you see used in the source already.** 17 | 18 | ### PhantomJS 19 | While Grunt can run the included unit tests via [PhantomJS](http://phantomjs.org/), this shouldn't be considered a substitute for the real thing. Please be sure to test the `test/*.html` unit test file(s) in _actual_ browsers. 20 | 21 | ## Modifying the code 22 | First, ensure that you have the latest [Node.js](http://nodejs.org/) and [npm](http://npmjs.org/) installed. 23 | 24 | Test that Grunt's CLI and Bower are installed by running `grunt --version` and `bower --version`. If the commands aren't found, run `npm install -g grunt-cli bower`. For more information about installing the tools, see the [getting started with Grunt guide](http://gruntjs.com/getting-started) or [bower.io](http://bower.io/) respectively. 25 | 26 | 1. Fork and clone the repo. 27 | 1. Run `npm install` to install all build dependencies (including Grunt). 28 | 1. Run `bower install` to install the front-end dependencies. 29 | 1. Run `grunt` to grunt this project. 30 | 31 | Assuming that you don't see any red, you're ready to go. Just be sure to run `grunt` after making any changes, to ensure that nothing is broken. 32 | 33 | ## Submitting pull requests 34 | 35 | 1. Create a new branch, please don't work in your `master` branch directly. 36 | 1. Add failing tests for the change you want to make. Run `grunt` to see the tests fail. 37 | 1. Fix stuff. 38 | 1. Run `grunt` to see if the tests pass. Repeat steps 2-4 until done. 39 | 1. Open `test/*.html` unit test file(s) in actual browser to ensure tests pass everywhere. 40 | 1. Update the documentation to reflect any changes. 41 | 1. Push to your fork and submit a pull request. 42 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | // Load all grunt tasks 5 | require('load-grunt-tasks')(grunt); 6 | // Show elapsed time at the end 7 | require('time-grunt')(grunt); 8 | 9 | // Project configuration. 10 | grunt.initConfig({ 11 | // Metadata. 12 | pkg: grunt.file.readJSON('_unified_manifest.json'), 13 | banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + 14 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 15 | '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + 16 | '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= ' + 17 | 'pkg.author.name %>;' + 18 | ' Licensed MIT */\n', 19 | // Task configuration. 20 | clean: { 21 | files: ['dist'] 22 | }, 23 | concat: { 24 | options: { 25 | banner: '<%= banner %>', 26 | stripBanners: true 27 | }, 28 | dist: { 29 | src: ['src/<%= pkg.name.replace(\'-\', \'.\') %>.js'], 30 | dest: 'dist/<%= pkg.name.replace(\'-\', \'.\') %>.js' 31 | } 32 | }, 33 | uglify: { 34 | options: { 35 | banner: '<%= banner %>' 36 | }, 37 | dist: { 38 | src: '<%= concat.dist.dest %>', 39 | dest: 'dist/<%= pkg.name.replace(\'-\', \'.\') %>.min.js' 40 | } 41 | }, 42 | karma: { 43 | options: { 44 | browsers: ['Chrome'], // @todo , 'Firefox'], 45 | configFile: 'karma.conf.js', 46 | singleRun: true 47 | }, 48 | src: { 49 | }, 50 | dist: { 51 | 52 | /* Override files list. Should be able to extend list from Karma config 53 | * but it's not working... @todo 54 | */ 55 | files: [ 56 | { 57 | src: ['bower_components/fontawesome/fonts/*.*'], 58 | served: true, 59 | included: false 60 | }, 61 | { 62 | src: ['test/awesome-cursor-test-font/fonts/*.*'], 63 | served: true, 64 | included: false 65 | }, 66 | { src: ['bower_components/fontawesome/**/*.css'] }, 67 | { src: ['test/awesome-cursor-test-font/style.css'] }, 68 | { src: ['test/**/*.png'], served: true, included: false }, 69 | { src: ['bower_components/jquery/dist/jquery.js'] }, 70 | { src: ['dist/*.min.js'] }, 71 | { src: ['test/*.js'] } 72 | ] 73 | }, 74 | watch: { 75 | singleRun: false 76 | } 77 | }, 78 | jshint: { 79 | options: { 80 | reporter: require('jshint-stylish') 81 | }, 82 | gruntfile: { 83 | options: { 84 | jshintrc: '.jshintrc' 85 | }, 86 | src: 'Gruntfile.js' 87 | }, 88 | src: { 89 | options: { 90 | jshintrc: 'src/.jshintrc' 91 | }, 92 | src: ['src/**/*.js'] 93 | }, 94 | test: { 95 | options: { 96 | jshintrc: 'test/.jshintrc' 97 | }, 98 | src: ['test/**/*.js'] 99 | } 100 | }, 101 | watch: { 102 | gruntfile: { 103 | files: '<%= jshint.gruntfile.src %>', 104 | tasks: ['jshint:gruntfile'] 105 | }, 106 | src: { 107 | files: '<%= jshint.src.src %>', 108 | tasks: ['jshint:src', 'qunit'] 109 | }, 110 | test: { 111 | files: '<%= jshint.test.src %>', 112 | tasks: ['jshint:test', 'qunit'] 113 | } 114 | }, 115 | unifiedmanifest: { 116 | all: { 117 | files: { 118 | './': '_unified_manifest.json' 119 | } 120 | } 121 | } 122 | }); 123 | 124 | // Default task. 125 | grunt.registerTask('default', [ 126 | 'jshint', 'unifiedmanifest', 'clean', 'concat', 'uglify', 'karma:src', 127 | 'karma:dist' 128 | ]); 129 | 130 | grunt.registerTask('test', ['karma:src']); 131 | }; 132 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 James Warwood 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery Awesome Cursor plugin [![GitHub version](https://badge.fury.io/gh/jwarby%2Fjquery-awesome-cursor.svg)](http://badge.fury.io/gh/jwarby%2Fjquery-awesome-cursor) 2 | 3 | [![Build Status](https://secure.travis-ci.org/jwarby/jquery-awesome-cursor.png?branch=master)](https://travis-ci.org/jwarby/jquery-awesome-cursor) 4 | [![Dependency Status](https://david-dm.org/jwarby/jquery-awesome-cursor/peer-status.svg?style=flat)](https://david-dm.org/jwarby/jquery-awesome-cursor#info=peerDependencies) 5 | [![devDependency Status](https://david-dm.org/jwarby/jquery-awesome-cursor/dev-status.svg?style=flat)](https://david-dm.org/jwarby/jquery-awesome-cursor#info=devDependencies) 6 | 7 | A jQuery plugin for using FontAwesome icons as custom CSS cursors. Also supports using a custom icon font instead of FontAwesome. 8 | 9 | See for the full documentation and demos. 10 | 11 | ```javascript 12 | $('body').awesomeCursor('pencil'); 13 | ``` 14 | 15 | Requires [jQuery](http://jquery.com) and [FontAwesome](http://fontawesome.io). 16 | 17 | ## Getting started 18 | 19 | ### Installing the plugin 20 | 21 | #### via `npm` (recommended) 22 | 23 | ```shell 24 | npm install jquery-awesome-cursor 25 | ``` 26 | 27 | ```html 28 | 29 | 30 | 31 | ``` 32 | 33 | From `v0.3.0` onwards, FontAwesome is marked as an optional dependency. If you plan on using `jquery-awesome-cursor` 34 | with a different icon font, you can skip installation of any optional dependencies like this: 35 | 36 | ```shell 37 | npm install --no-optional jquery-awesome-cursor 38 | ``` 39 | 40 | ##### IMPORTANT: npm@3 won't install `peerDependencies` anymore, so if you haven't already installed jQuery you will get an `UNMET PEER DEPENDENCY` warning when you install. To fix, just do `npm install jquery`. 41 | 42 | #### via `bower` 43 | 44 | ```shell 45 | bower install jquery-awesome-cursor 46 | ``` 47 | 48 | ```html 49 | 50 | 51 | 52 | ``` 53 | ##### IMPORTANT: from `v0.3.0` onwards, you need to bring your own FontAwesome! FontAwesome is considered an optional dependency, but Bower doesn't support optional dependencies - so you must install FontAwesome yourself. See [#21](https://github.com/jwarby/jquery-awesome-cursor/issues/21). 54 | 55 | ```shell 56 | bower install font-awesome 57 | ``` 58 | 59 | #### Manual installation 60 | 61 | Download the [production version][min] or the [development version][max]. You will need to download and install FontAwesome manually as well. 62 | 63 | [min]: https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/master/dist/jquery.awesome-cursor.min.js 64 | [max]: https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/master/dist/jquery.awesome-cursor.js 65 | 66 | In your web page: 67 | 68 | ```html 69 | 70 | 71 | 72 | ``` 73 | 74 | #### RawGit CDN 75 | 76 | You could also use [RawGit](https://rawgit.com)'s CDN: 77 | 78 | ```html 79 | 80 | 81 | 82 | ``` 83 | ## Documentation 84 | 85 | ### Setting a cursor 86 | 87 | You can set a FontAwesome cursor on any element by calling `awesomeCursor`, and passing the name of the icon you 88 | want to use: 89 | 90 | ```javascript 91 | $('body').awesomeCursor(''); 92 | ``` 93 | 94 | See for a list of available icons. 95 | 96 | ### Setting cursor options 97 | 98 | #### Colour 99 | 100 | Cursors can be any color you want, specified as a CSS color: 101 | 102 | ```javascript 103 | $('body').awesomeCursor('eyedropper', { 104 | color: '#ff0000' 105 | }) 106 | ``` 107 | 108 | ```javascript 109 | $('body').awesomeCursor('eyedropper', { 110 | color: 'rgba(255, 255, 255, 0.75)' 111 | }) 112 | ``` 113 | 114 | ```javascript 115 | $('body').awesomeCursor('eyedropper', { 116 | color: 'cyan' 117 | }); 118 | ``` 119 | 120 | ```javascript 121 | $('body').awesomeCursor('eyedropper', { 122 | color: 'hsl(90, 100%, 50%)' 123 | }); 124 | ``` 125 | 126 | #### Size 127 | 128 | Cursors can be any size (specified in pixels): 129 | 130 | ```javascript 131 | $('body').awesomeCursor('pencil', { 132 | size: 32 133 | }); 134 | ``` 135 | 136 | Only pixel values are supported, as CSS cursor hotspots can only be specified in pixels. 137 | 138 | #### Hotspot 139 | 140 | The hotspot for a cursor can be defined, with an array containing the hotspot's x and y offsets: 141 | 142 | ```javascript 143 | $('body').awesomeCursor('pencil', { 144 | hotspot: [0, 17] 145 | }); 146 | ``` 147 | 148 | Or, using a string descriptor: 149 | 150 | ```javascript 151 | $('body').awesomeCursor('pencil', { 152 | hotspot: 'bottom left' 153 | }); 154 | ``` 155 | 156 | ##### String descriptors 157 | 158 | The following values can be used in the hotspot string descriptor: 159 | 160 | - `'center'`: positions the hotspot's x and y offset in the center of the cursor 161 | - `'left'` : positions the hotspot's x offset on the left of the cursor (equivalent to 0) 162 | - `'right'` : positions the hotspot's x offset on the far right of the cursor (equivalent to cursorSize - 1) 163 | - `'top'` : positions the hotspot's y offset at the top of the cursor (equivalent to 0) 164 | - `'bottom'`: positions the hotspot's y offset at the bottom of the cursor (equivalent to cursorSize - 1) 165 | 166 | The descriptors can be combined by space-separating them, e.g.: 167 | 168 | - `'top left'` 169 | - `'center left'` 170 | - `'bottom right'` 171 | - `'top right'` 172 | - etc. 173 | 174 | #### Flip 175 | 176 | Cursors can be flipped horizontally, vertically, or in both directions, by setting the `flip` option: 177 | 178 | ```javascript 179 | // Horizontal flip 180 | $('body').awesomeCursor('pencil', { 181 | flip: 'horizontal' 182 | }); 183 | 184 | // Vertical flip 185 | $('body').awesomeCursor('pencil', { 186 | flip: 'vertical' 187 | }); 188 | 189 | // Horizontal and Vertical flip 190 | $('body').awesomeCursor('pencil', { 191 | flip: 'both' 192 | }); 193 | ``` 194 | 195 | #### Rotate 196 | 197 | A cursor can be rotated any number of degrees using the `rotate` option: 198 | 199 | ```javascript 200 | // 45 degrees clockwise 201 | $('body').awesomeCursor('pencil', { 202 | rotate: 45 203 | }); 204 | 205 | // 105 degrees anti-clockwise 206 | $('body').awesomeCursor('pencil', { 207 | rotate: -105 208 | }); 209 | ``` 210 | 211 | #### Outline 212 | 213 | A cursor can be outlined in any color by setting the `outline` option to any valid CSS color: 214 | 215 | ```javascript 216 | // Red outline 217 | $('body').awesomeCursor('pencil', { 218 | outline: 'red' 219 | }); 220 | 221 | // White outline 222 | $('body').awesomeCursor('pencil', { 223 | outline: 'white' 224 | }); 225 | ``` 226 | 227 | #### Using a different icon font 228 | 229 | As of `v0.1.0`, a different icon font instead of FontAwesome can be used. To use a different font, the `font` option must be set to 230 | an object, specifying the font family and the CSS class format for icons. The example below shows how [typicons](http://typicons.com) 231 | icons can be used instead of FontAwesome icons: 232 | 233 | ```javascript 234 | // Using 'typicons' icons instead of FontAwesome 235 | $('body').awesomecursor('brush', { 236 | font: { 237 | family: 'typicons', 238 | cssClass: 'typcn typcn-%s' // '%s' is the icon name ('brush') 239 | } 240 | }); 241 | ``` 242 | 243 | In the above example, we set the font `family` to 'typicons', and set the `cssClass` to the format that `typicons` uses. `%s` denotes the 244 | icon name that is passed as the first argument to `awesomeCursor`. 245 | 246 | The `cssClass` option can either be a string, as shown above, or a function: 247 | 248 | ```javascript 249 | // Using 'typicons' instead of fontawesome 250 | $('body').awesomecursor('brush', { 251 | font: { 252 | family: 'typicons', 253 | cssClass: function(name) { 254 | 255 | // `name` is 'brush' 256 | return 'typcn typcn-' + name; 257 | } 258 | } 259 | }); 260 | ``` 261 | 262 | You may want to set your replacement icon font as the default: 263 | 264 | ```javascript 265 | // Use 'typicons' as default 266 | $.fn.awesomeCursor.defaults.font = { 267 | family: 'typicons', 268 | cssClass: 'typcn typcn-%s' 269 | }; 270 | ``` 271 | **Note: the replacement icon font must use `:before` pseudo elements with unicode content** 272 | 273 | ## Examples 274 | 275 | ```javascript 276 | /* Set the body element's cursor to a green pencil, with the hotspot located at the bottom left of the cursor (where the 277 | * pencil tip is): 278 | */ 279 | $('body').awesomeCursor('pencil', { 280 | color: 'green', 281 | hotspot: 'bottom left' 282 | }); 283 | 284 | // Set the cursor to be a big blue location arrow icon: 285 | $('body').awesomeCursor('location-arrow', { 286 | color: '#0050FF', 287 | hotspot: 'top right', 288 | size: 48 289 | }); 290 | 291 | // Set the cursor to be a horizontally flipped version of the location arrow 292 | $('body').awesomeCursor('location-arrow', { 293 | color: '#0050FF', 294 | hotspot: 'top right', 295 | size: 48, 296 | flip: 'horizontal' 297 | }); 298 | 299 | // Set the cursor to be a red rotated left arrow 300 | $('body').awesomeCursor('long-arrow-left', { 301 | color: 'red', 302 | hotspot: 'top left', 303 | rotate: 45, 304 | }); 305 | 306 | // Set the cursor to a black eraser icon with a red outline 307 | $('body').awesomeCursor('eraser', { 308 | color: 'black', 309 | outline: 'red' 310 | }); 311 | ``` 312 | 313 | ## Browser Support 314 | 315 | - Chrome 316 | - Firefox 317 | 318 | ## Bugs and Feature Requests 319 | 320 | - 321 | 322 | ## Contributing 323 | 324 | See [CONTRIBUTING.md](https://github.com/jwarby/jquery-awesome-cursor/blob/master/CONTRIBUTING.md) 325 | 326 | ## Roadmap 327 | 328 | - ~~Allow cursors to be flipped vertically and/or horizontally~~ [✔ v0.0.2](https://github.com/jwarby/jquery-awesome-cursor/releases/tag/v0.0.2) 329 | - ~~Allow cursors to be rotated by an abitrary number of degrees~~ [✔ v0.0.4](https://github.com/jwarby/jquery-awesome-cursor/releases/tag/v0.0.4) 330 | - ~~Optional outlines for cursors~~ [✔ v0.0.5](https://github.com/jwarby/jquery-awesome-cursor/releases/tag/v0.0.5) 331 | - ~~Allow a different icon font to FontAwesome to be used~~ [✔ v0.1.0](https://github.com/jwarby/jquery-awesome-cursor/releases/tag/v0.1.0) 332 | - IE11 support (if possible) 333 | - ~~Data API (under consideration)~~ [✕ Not implemented](https://github.com/jwarby/jquery-awesome-cursor/issues/6) 334 | - ~~Support for composite cursors made of up of multiple icons, a la FontAwesome stacked icons (under consideration)~~ [✕ Not implemented](https://github.com/jwarby/jquery-awesome-cursor/issues/7) 335 | 336 | ## Release History 337 | 338 | - `v0.3.1` - `23rd Apr 2016` - Hotfix - change TravisCI node test versions to v4 and v5; add dotfiles back into bower ignore field 339 | - `v0.3.0` - `23rd Apr 2016` - Make FontAwesome an optional dependency ([#21](https://github.com/jwarby/jquery-awesome-cursor/issues/21)); add an .npmignore file, update bower ignore field; updated devDependencies 340 | - `v0.2.0` - `28th Mar 2016` - Bug fix for certain icons getting clipped when used as a cursor ([#19](https://github.com/jwarby/jquery-awesome-cursor/issues/19)) 341 | - `v0.1.5` - ` 9th Dec 2015` - Add `main` to package.json, allowing plugin to be used properly when installed via npm and used with tools such as Browserify 342 | - `v0.1.4` - `23rd Oct 2015` - Remove minor version constraints on bower/npm dependencies 343 | - `v0.1.3` - `19th Oct 2015` - Bug fix for Chrome not updating cursor if cursor had already been set, run tests using Karma, using SPDX format for "license" field in package.json 344 | - `v0.1.2` - `25th May 2015` - Bug fix for rendering artifacts in Chrome ([#14](https://github.com/jwarby/jquery-awesome-cursor/issues/14)) 345 | - `v0.1.1` - ` 5th Mar 2015` - Bug fix for rendering artifacts in Firefox ([#10](https://github.com/jwarby/jquery-awesome-cursor/issues/10)) 346 | - `v0.1.0` - `17th Dec 2014` - Add `font` option for specifying an alternative icon font to use instead of FontAwesome 347 | - `v0.0.5` - `12th Dec 2014` - Add `outline` option for specifying an outline color for cursors 348 | - `v0.0.4` - `29th Nov 2014` - Add `rotate` option for rotating cursor by a specified number of degrees. Flip transformation now 349 | applied after other transformations (e.g. rotate) 350 | - `v0.0.3` - `17th Nov 2014` - Fix an error in the README file 351 | - `v0.0.2` - `17th Nov 2014` - Add the `flip` option to allow cursors to be flipped horizontally, vertically, or in both directions 352 | - `v0.0.1` - `10th Nov 2014` - First version 353 | -------------------------------------------------------------------------------- /_unified_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-awesome-cursor", 3 | "version": "0.3.1", 4 | "description": "jQuery plugin for using FontAwesome icons as custom CSS cursors", 5 | "keywords": [ 6 | "jquery-plugin", 7 | "fontawesome", 8 | "cursor", 9 | "custom", 10 | "css" 11 | ], 12 | "homepage": "https://jwarby.github.io/jquery-awesome-cursor", 13 | "bugs": { 14 | "url": "http://github.com/jwarby/jquery-awesome-cursor/issues" 15 | }, 16 | "author": "James Warwood (https://github.com/jwarby)", 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/jwarby/jquery-awesome-cursor.git" 20 | }, 21 | "license": "MIT", 22 | "_package.json_": { 23 | "devDependencies": { 24 | "bower": "^1.7.9", 25 | "grunt": "^1.0.1", 26 | "grunt-contrib-clean": "^1.0.0", 27 | "grunt-contrib-concat": "^1.0.1", 28 | "grunt-contrib-connect": "^1.0.1", 29 | "grunt-contrib-jshint": "^1.0.0", 30 | "grunt-contrib-qunit": "^1.2.0", 31 | "grunt-contrib-uglify": "^1.0.1", 32 | "grunt-contrib-watch": "^1.0.0", 33 | "grunt-karma": "^0.12.2", 34 | "grunt-unified-manifest": "git://github.com/jwarby/grunt-unified-manifest.git", 35 | "jquery": "^2.2.3", 36 | "jshint-stylish": "^2.1.0", 37 | "karma": "^0.13.22", 38 | "karma-chrome-launcher": "^0.2.3", 39 | "karma-firefox-launcher": "^0.1.7", 40 | "karma-qunit": "^0.1.9", 41 | "load-grunt-tasks": "^3.5.0", 42 | "qunitjs": "^2.0.0-rc1", 43 | "time-grunt": "^1.3.0" 44 | }, 45 | "optionalDependencies": { 46 | "font-awesome": "4.x" 47 | }, 48 | "peerDependencies": { 49 | "jquery": "2.x" 50 | }, 51 | "engines": { 52 | "node": ">=0.10.0" 53 | }, 54 | "main": "dist/jquery.awesome-cursor", 55 | "scripts": { 56 | "test": "grunt test" 57 | }, 58 | "directories": { 59 | "test": "test" 60 | } 61 | }, 62 | "_bower.json_": { 63 | "main": "dist/jquery.awesome-cursor.js", 64 | "ignore": [ 65 | "node_modules", 66 | "bower_components", 67 | "test", 68 | "src", 69 | "package.json", 70 | "_unified_manifest.json", 71 | "Gruntfile.js", 72 | "karma.conf.js", 73 | "**/.*" 74 | ], 75 | "dependencies": { 76 | "jquery": "2.x" 77 | }, 78 | "devDependencies": { 79 | "qunit": "1.x", 80 | "jquery": "2.x" 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-awesome-cursor", 3 | "version": "0.3.1", 4 | "description": "jQuery plugin for using FontAwesome icons as custom CSS cursors", 5 | "keywords": [ 6 | "jquery-plugin", 7 | "fontawesome", 8 | "cursor", 9 | "custom", 10 | "css" 11 | ], 12 | "homepage": "https://jwarby.github.io/jquery-awesome-cursor", 13 | "bugs": { 14 | "url": "http://github.com/jwarby/jquery-awesome-cursor/issues" 15 | }, 16 | "author": "James Warwood (https://github.com/jwarby)", 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/jwarby/jquery-awesome-cursor.git" 20 | }, 21 | "license": "MIT", 22 | "main": "dist/jquery.awesome-cursor.js", 23 | "ignore": [ 24 | "node_modules", 25 | "bower_components", 26 | "test", 27 | "src", 28 | "package.json", 29 | "_unified_manifest.json", 30 | "Gruntfile.js", 31 | "karma.conf.js", 32 | "**/.*" 33 | ], 34 | "dependencies": { 35 | "jquery": "2.x" 36 | }, 37 | "devDependencies": { 38 | "qunit": "1.x", 39 | "jquery": "2.x" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /dist/jquery.awesome-cursor.js: -------------------------------------------------------------------------------- 1 | /*! jquery-awesome-cursor - v0.3.1 - 2016-04-23 2 | * https://jwarby.github.io/jquery-awesome-cursor 3 | * Copyright (c) 2016 ; Licensed MIT */ 4 | ;(function(global, factory) { 5 | if (typeof define === 'function' && define.amd) { 6 | define(['jquery'], factory); 7 | } else if (typeof exports === 'object') { 8 | factory(require('jquery')); 9 | } else { 10 | factory(global.jQuery); 11 | } 12 | })(this, function($) { 13 | 'use strict'; 14 | 15 | /** 16 | * Parse the user-supplied hotspot string. Hotspot values as strings are used 17 | * to set the cursor based on a human-readable value. 18 | * 19 | * ## Examples 20 | * 21 | * - `hotspot: 'center'`: the hotspot is in the center of the cursor 22 | * - `hotspot: 'center left'`: the hotspot is centered vertically, and fixed 23 | * to the left of the cursor horizontally 24 | * - `hotspot: 'top right'`: the hotspot is at the top right 25 | * - `hotspot: 'center top'`: the hotspot is centered horizontally, and fixed 26 | * to the top of the cursor vertically 27 | * 28 | * @param {String} hotspot The string descriptor for the hotspot location 29 | * @param {Number} size The size of the cursor 30 | * 31 | * @return {[Number]} an array with two elements, the x and y offsets for the 32 | * hotspot 33 | * 34 | * @throws {Error} if `hotspot` is not a string, or `cursorSize` is not a 35 | * number 36 | */ 37 | var parseHotspotString = function(hotspot, cursorSize) { 38 | var xOffset = 0, 39 | yOffset = 0; 40 | 41 | if (typeof hotspot !== 'string') { 42 | $.error('Hotspot value is not a string and could not be parsed'); 43 | } 44 | 45 | if (typeof cursorSize !== 'number') { 46 | $.error('Cursor size must be a number'); 47 | } 48 | 49 | hotspot.split(' ').forEach(function(part) { 50 | switch (part) { 51 | case 'center': 52 | xOffset = cursorSize / 2; 53 | yOffset = cursorSize / 2; 54 | break; 55 | case 'top': 56 | yOffset = 0; 57 | break; 58 | case 'bottom': 59 | 60 | /* Browsers will default to 0 0 if yOffset is the very last pixel, 61 | * hence - 1 62 | */ 63 | yOffset = cursorSize - 1; 64 | break; 65 | case 'left': 66 | xOffset = 0; 67 | break; 68 | case 'right': 69 | xOffset = cursorSize - 1; 70 | break; 71 | } 72 | }); 73 | 74 | return [xOffset, yOffset]; 75 | }; 76 | 77 | /** 78 | * Returns a new canvas with the same contents as `canvas`, flipped 79 | * accordingly. 80 | * 81 | * @param {Canvas} canvas The canvas to flip 82 | * @param {String} direction The direction flip the canvas in. Can be one 83 | * of: 84 | * - 'horizontal' 85 | * - 'vertical' 86 | * - 'both' 87 | * 88 | * @return {Canvas} a new canvas with the flipped contents of the input canvas 89 | */ 90 | function flipCanvas(canvas, direction) { 91 | if ($.inArray(direction, ['horizontal', 'vertical', 'both']) === -1) { 92 | $.error('Flip value must be one of horizontal, vertical or both'); 93 | } 94 | 95 | var flippedCanvas = $('')[0], 96 | flippedContext; 97 | 98 | flippedCanvas.width = canvas.width; 99 | flippedCanvas.height = canvas.height; 100 | 101 | flippedContext = flippedCanvas.getContext('2d'); 102 | 103 | if (direction === 'horizontal' || direction === 'both') { 104 | flippedContext.translate(canvas.width, 0); 105 | flippedContext.scale(-1, 1); 106 | } 107 | 108 | if (direction === 'vertical' || direction === 'both') { 109 | flippedContext.translate(0, canvas.height); 110 | flippedContext.scale(1, -1); 111 | } 112 | 113 | flippedContext.drawImage(canvas, 0, 0, canvas.width, canvas.height); 114 | 115 | return flippedCanvas; 116 | } 117 | 118 | $.fn.extend({ 119 | awesomeCursor: function(iconName, options) { 120 | options = $.extend({}, $.fn.awesomeCursor.defaults, options); 121 | 122 | if (typeof iconName !== 'string' || !iconName) { 123 | $.error('First parameter must be the icon name, e.g. \'pencil\''); 124 | } 125 | 126 | options.size = typeof options.size === 'string' ? 127 | parseInt(options.size, 10) : options.size; 128 | 129 | if (typeof options.hotspot === 'string') { 130 | options.hotspot = parseHotspotString(options.hotspot, options.size); 131 | } 132 | 133 | // Clamp hotspot coordinates between 0 and size - 1 134 | options.hotspot = $.map(options.hotspot, function(coordinate) { 135 | return Math.min(options.size - 1, Math.max(0, coordinate)); 136 | }); 137 | 138 | var cssClass = (function(name, template) { 139 | if (typeof template === 'string') { 140 | return template.replace(/%s/g, name); 141 | } else if (typeof template === 'function') { 142 | return template(name); 143 | } 144 | 145 | return name; 146 | })(iconName, options.font.cssClass), 147 | srcElement = $('', { 148 | class: cssClass, 149 | style: 'display: inline; font-size: ' + options.size + 'px;' 150 | }); 151 | 152 | // Wrap the icon inside an absolute element to remove it from doc flow 153 | var wrapper = $('
', { 154 | style: 'position: absolute; left: -9999px; top: -9999px;' 155 | }).append(srcElement); 156 | 157 | // Render element to the DOM, otherwise `getComputedStyle` will not work 158 | $('body').append(wrapper); 159 | 160 | // Get the unicode value and dimensions of the icon 161 | var unicode = window.getComputedStyle(srcElement[0], ':before') 162 | .getPropertyValue('content'), 163 | clientRect = srcElement[0].getBoundingClientRect(); 164 | 165 | var canvas = $('')[0], 166 | canvasSize = Math.max(clientRect.width, clientRect.height), 167 | hotspotOffset, dataURL, context; 168 | 169 | // Remove the source element from the DOM 170 | srcElement.remove(); 171 | 172 | // Increase the size of the canvas to account for the cursor's outline 173 | if (options.outline) { 174 | canvasSize += 2; 175 | } 176 | 177 | if (options.rotate) { 178 | 179 | // @TODO: move this into it's own function 180 | canvasSize = Math.ceil(Math.sqrt( 181 | Math.pow(canvasSize, 2) + Math.pow(canvasSize, 2) 182 | )); 183 | 184 | hotspotOffset = (canvasSize - options.size) / 2; 185 | canvas.width = canvasSize; 186 | canvas.height = canvasSize; 187 | 188 | context = canvas.getContext('2d'); 189 | context.translate(canvas.width / 2, canvas.height / 2); 190 | 191 | // Canvas API works in radians, not degrees, hence `* Math.PI / 180` 192 | context.rotate(options.rotate * Math.PI / 180); 193 | context.translate(-canvas.width / 2, -canvas.height / 2); 194 | 195 | // Translate hotspot offset 196 | options.hotspot[0] += options.hotspot[0] !== canvas.width / 2 ? 197 | hotspotOffset : 0; 198 | 199 | options.hotspot[1] += options.hotspot[1] !== canvas.height / 2 ? 200 | hotspotOffset : 0; 201 | } else { 202 | 203 | canvas.height = canvasSize; 204 | canvas.width = canvasSize; 205 | 206 | context = canvas.getContext('2d'); 207 | } 208 | 209 | /* Firefox wraps the extracted unicode value in double quotes - #10 210 | * Chrome 43+ is wrapping the extracted value in single quotes - #14 211 | */ 212 | unicode = unicode.replace(/['"]/g, ''); 213 | 214 | // Draw the cursor to the canvas 215 | context.fillStyle = options.color; 216 | context.font = options.size + 'px ' + options.font.family; 217 | context.textAlign = 'center'; 218 | context.textBaseline = 'middle'; 219 | context.fillText(unicode, canvasSize / 2, canvasSize / 2); 220 | 221 | // Check for outline option 222 | if (options.outline) { 223 | context.lineWidth = 0.5; 224 | context.strokeStyle = options.outline; 225 | context.strokeText(unicode, canvasSize / 2, canvasSize / 2); 226 | } 227 | 228 | // Check flip option 229 | if (options.flip) { 230 | canvas = flipCanvas(canvas, options.flip); 231 | } 232 | 233 | dataURL = canvas.toDataURL('image/png'); 234 | 235 | $(this) 236 | 237 | // Fixes issue with Chrome not setting cursor if already set 238 | .css('cursor', '') 239 | .css('cursor', [ 240 | 'url(' + dataURL + ')', 241 | options.hotspot[0], 242 | options.hotspot[1], 243 | ',', 244 | 'auto' 245 | ].join(' ')) 246 | ; 247 | 248 | // Maintain chaining 249 | return this; 250 | } 251 | }); 252 | 253 | // Expose the defaults so that users can override them if they want to 254 | $.fn.awesomeCursor.defaults = { 255 | color: '#000000', 256 | size: 18, 257 | hotspot: [0, 0], 258 | flip: '', 259 | rotate: 0, 260 | outline: null, 261 | font: { 262 | family: 'FontAwesome', 263 | cssClass: 'fa fa-%s' 264 | } 265 | }; 266 | }); 267 | -------------------------------------------------------------------------------- /dist/jquery.awesome-cursor.min.js: -------------------------------------------------------------------------------- 1 | /*! jquery-awesome-cursor - v0.3.1 - 2016-04-23 2 | * https://jwarby.github.io/jquery-awesome-cursor 3 | * Copyright (c) 2016 ; Licensed MIT */ 4 | !function(a,b){"function"==typeof define&&define.amd?define(["jquery"],b):b("object"==typeof exports?require("jquery"):a.jQuery)}(this,function(a){"use strict";function b(b,c){-1===a.inArray(c,["horizontal","vertical","both"])&&a.error("Flip value must be one of horizontal, vertical or both");var d,e=a("")[0];return e.width=b.width,e.height=b.height,d=e.getContext("2d"),"horizontal"!==c&&"both"!==c||(d.translate(b.width,0),d.scale(-1,1)),"vertical"!==c&&"both"!==c||(d.translate(0,b.height),d.scale(1,-1)),d.drawImage(b,0,0,b.width,b.height),e}var c=function(b,c){var d=0,e=0;return"string"!=typeof b&&a.error("Hotspot value is not a string and could not be parsed"),"number"!=typeof c&&a.error("Cursor size must be a number"),b.split(" ").forEach(function(a){switch(a){case"center":d=c/2,e=c/2;break;case"top":e=0;break;case"bottom":e=c-1;break;case"left":d=0;break;case"right":d=c-1}}),[d,e]};a.fn.extend({awesomeCursor:function(d,e){e=a.extend({},a.fn.awesomeCursor.defaults,e),"string"==typeof d&&d||a.error("First parameter must be the icon name, e.g. 'pencil'"),e.size="string"==typeof e.size?parseInt(e.size,10):e.size,"string"==typeof e.hotspot&&(e.hotspot=c(e.hotspot,e.size)),e.hotspot=a.map(e.hotspot,function(a){return Math.min(e.size-1,Math.max(0,a))});var f=function(a,b){return"string"==typeof b?b.replace(/%s/g,a):"function"==typeof b?b(a):a}(d,e.font.cssClass),g=a("",{"class":f,style:"display: inline; font-size: "+e.size+"px;"}),h=a("
",{style:"position: absolute; left: -9999px; top: -9999px;"}).append(g);a("body").append(h);var i,j,k,l=window.getComputedStyle(g[0],":before").getPropertyValue("content"),m=g[0].getBoundingClientRect(),n=a("")[0],o=Math.max(m.width,m.height);return g.remove(),e.outline&&(o+=2),e.rotate?(o=Math.ceil(Math.sqrt(Math.pow(o,2)+Math.pow(o,2))),i=(o-e.size)/2,n.width=o,n.height=o,k=n.getContext("2d"),k.translate(n.width/2,n.height/2),k.rotate(e.rotate*Math.PI/180),k.translate(-n.width/2,-n.height/2),e.hotspot[0]+=e.hotspot[0]!==n.width/2?i:0,e.hotspot[1]+=e.hotspot[1]!==n.height/2?i:0):(n.height=o,n.width=o,k=n.getContext("2d")),l=l.replace(/['"]/g,""),k.fillStyle=e.color,k.font=e.size+"px "+e.font.family,k.textAlign="center",k.textBaseline="middle",k.fillText(l,o/2,o/2),e.outline&&(k.lineWidth=.5,k.strokeStyle=e.outline,k.strokeText(l,o/2,o/2)),e.flip&&(n=b(n,e.flip)),j=n.toDataURL("image/png"),a(this).css("cursor","").css("cursor",["url("+j+")",e.hotspot[0],e.hotspot[1],",","auto"].join(" ")),this}}),a.fn.awesomeCursor.defaults={color:"#000000",size:18,hotspot:[0,0],flip:"",rotate:0,outline:null,font:{family:"FontAwesome",cssClass:"fa fa-%s"}}}); -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Sat Jun 06 2015 15:48:50 GMT+0800 (MYT) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['qunit'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | { 19 | pattern: 'bower_components/fontawesome/fonts/*.*', 20 | served: true, 21 | included: false 22 | }, 23 | { 24 | pattern: 'test/awesome-cursor-test-font/fonts/*.*', 25 | served: true, 26 | included: false 27 | }, 28 | 'bower_components/fontawesome/**/*.css', 29 | 'test/awesome-cursor-test-font/style.css', 30 | { pattern: 'test/**/*.png', served: true, included: false }, 31 | 'bower_components/jquery/dist/jquery.js', 32 | 'src/*.js', 33 | 'test/*.js' 34 | ], 35 | 36 | proxies: { 37 | '/expected': 'http://localhost:9876/base/test/expected/' 38 | }, 39 | 40 | 41 | // list of files to exclude 42 | exclude: [ 43 | ], 44 | 45 | 46 | // preprocess matching files before serving them to the browser 47 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 48 | preprocessors: { 49 | }, 50 | 51 | 52 | // test results reporter to use 53 | // possible values: 'dots', 'progress' 54 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 55 | reporters: ['progress'], 56 | 57 | 58 | // web server port 59 | port: 9876, 60 | 61 | 62 | // enable / disable colors in the output (reporters and logs) 63 | colors: true, 64 | 65 | 66 | // level of logging 67 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 68 | logLevel: config.LOG_INFO, 69 | 70 | 71 | // enable / disable watching file and executing tests whenever any file changes 72 | autoWatch: false, 73 | 74 | 75 | // start these browsers 76 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 77 | browsers: ['Chrome'],//, 'Firefox'], 78 | 79 | 80 | // Continuous Integration mode 81 | // if true, Karma captures browsers, runs the tests and exits 82 | singleRun: false 83 | }); 84 | }; 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-awesome-cursor", 3 | "version": "0.3.1", 4 | "description": "jQuery plugin for using FontAwesome icons as custom CSS cursors", 5 | "keywords": [ 6 | "jquery-plugin", 7 | "fontawesome", 8 | "cursor", 9 | "custom", 10 | "css" 11 | ], 12 | "homepage": "https://jwarby.github.io/jquery-awesome-cursor", 13 | "bugs": { 14 | "url": "http://github.com/jwarby/jquery-awesome-cursor/issues" 15 | }, 16 | "author": "James Warwood (https://github.com/jwarby)", 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/jwarby/jquery-awesome-cursor.git" 20 | }, 21 | "license": "MIT", 22 | "devDependencies": { 23 | "bower": "^1.7.9", 24 | "grunt": "^1.0.1", 25 | "grunt-contrib-clean": "^1.0.0", 26 | "grunt-contrib-concat": "^1.0.1", 27 | "grunt-contrib-connect": "^1.0.1", 28 | "grunt-contrib-jshint": "^1.0.0", 29 | "grunt-contrib-qunit": "^1.2.0", 30 | "grunt-contrib-uglify": "^1.0.1", 31 | "grunt-contrib-watch": "^1.0.0", 32 | "grunt-karma": "^0.12.2", 33 | "grunt-unified-manifest": "git://github.com/jwarby/grunt-unified-manifest.git", 34 | "jquery": "^2.2.3", 35 | "jshint-stylish": "^2.1.0", 36 | "karma": "^0.13.22", 37 | "karma-chrome-launcher": "^0.2.3", 38 | "karma-firefox-launcher": "^0.1.7", 39 | "karma-qunit": "^0.1.9", 40 | "load-grunt-tasks": "^3.5.0", 41 | "qunitjs": "^2.0.0-rc1", 42 | "time-grunt": "^1.3.0" 43 | }, 44 | "optionalDependencies": { 45 | "font-awesome": "4.x" 46 | }, 47 | "peerDependencies": { 48 | "jquery": "2.x" 49 | }, 50 | "engines": { 51 | "node": ">=0.10.0" 52 | }, 53 | "main": "dist/jquery.awesome-cursor", 54 | "scripts": { 55 | "test": "grunt test" 56 | }, 57 | "directories": { 58 | "test": "test" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/.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 | "browser": true, 14 | "predef": ["jQuery", "require", "define"] 15 | } 16 | -------------------------------------------------------------------------------- /src/jquery.awesome-cursor.js: -------------------------------------------------------------------------------- 1 | ;(function(global, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery'], factory); 4 | } else if (typeof exports === 'object') { 5 | factory(require('jquery')); 6 | } else { 7 | factory(global.jQuery); 8 | } 9 | })(this, function($) { 10 | 'use strict'; 11 | 12 | /** 13 | * Parse the user-supplied hotspot string. Hotspot values as strings are used 14 | * to set the cursor based on a human-readable value. 15 | * 16 | * ## Examples 17 | * 18 | * - `hotspot: 'center'`: the hotspot is in the center of the cursor 19 | * - `hotspot: 'center left'`: the hotspot is centered vertically, and fixed 20 | * to the left of the cursor horizontally 21 | * - `hotspot: 'top right'`: the hotspot is at the top right 22 | * - `hotspot: 'center top'`: the hotspot is centered horizontally, and fixed 23 | * to the top of the cursor vertically 24 | * 25 | * @param {String} hotspot The string descriptor for the hotspot location 26 | * @param {Number} size The size of the cursor 27 | * 28 | * @return {[Number]} an array with two elements, the x and y offsets for the 29 | * hotspot 30 | * 31 | * @throws {Error} if `hotspot` is not a string, or `cursorSize` is not a 32 | * number 33 | */ 34 | var parseHotspotString = function(hotspot, cursorSize) { 35 | var xOffset = 0, 36 | yOffset = 0; 37 | 38 | if (typeof hotspot !== 'string') { 39 | $.error('Hotspot value is not a string and could not be parsed'); 40 | } 41 | 42 | if (typeof cursorSize !== 'number') { 43 | $.error('Cursor size must be a number'); 44 | } 45 | 46 | hotspot.split(' ').forEach(function(part) { 47 | switch (part) { 48 | case 'center': 49 | xOffset = cursorSize / 2; 50 | yOffset = cursorSize / 2; 51 | break; 52 | case 'top': 53 | yOffset = 0; 54 | break; 55 | case 'bottom': 56 | 57 | /* Browsers will default to 0 0 if yOffset is the very last pixel, 58 | * hence - 1 59 | */ 60 | yOffset = cursorSize - 1; 61 | break; 62 | case 'left': 63 | xOffset = 0; 64 | break; 65 | case 'right': 66 | xOffset = cursorSize - 1; 67 | break; 68 | } 69 | }); 70 | 71 | return [xOffset, yOffset]; 72 | }; 73 | 74 | /** 75 | * Returns a new canvas with the same contents as `canvas`, flipped 76 | * accordingly. 77 | * 78 | * @param {Canvas} canvas The canvas to flip 79 | * @param {String} direction The direction flip the canvas in. Can be one 80 | * of: 81 | * - 'horizontal' 82 | * - 'vertical' 83 | * - 'both' 84 | * 85 | * @return {Canvas} a new canvas with the flipped contents of the input canvas 86 | */ 87 | function flipCanvas(canvas, direction) { 88 | if ($.inArray(direction, ['horizontal', 'vertical', 'both']) === -1) { 89 | $.error('Flip value must be one of horizontal, vertical or both'); 90 | } 91 | 92 | var flippedCanvas = $('')[0], 93 | flippedContext; 94 | 95 | flippedCanvas.width = canvas.width; 96 | flippedCanvas.height = canvas.height; 97 | 98 | flippedContext = flippedCanvas.getContext('2d'); 99 | 100 | if (direction === 'horizontal' || direction === 'both') { 101 | flippedContext.translate(canvas.width, 0); 102 | flippedContext.scale(-1, 1); 103 | } 104 | 105 | if (direction === 'vertical' || direction === 'both') { 106 | flippedContext.translate(0, canvas.height); 107 | flippedContext.scale(1, -1); 108 | } 109 | 110 | flippedContext.drawImage(canvas, 0, 0, canvas.width, canvas.height); 111 | 112 | return flippedCanvas; 113 | } 114 | 115 | $.fn.extend({ 116 | awesomeCursor: function(iconName, options) { 117 | options = $.extend({}, $.fn.awesomeCursor.defaults, options); 118 | 119 | if (typeof iconName !== 'string' || !iconName) { 120 | $.error('First parameter must be the icon name, e.g. \'pencil\''); 121 | } 122 | 123 | options.size = typeof options.size === 'string' ? 124 | parseInt(options.size, 10) : options.size; 125 | 126 | if (typeof options.hotspot === 'string') { 127 | options.hotspot = parseHotspotString(options.hotspot, options.size); 128 | } 129 | 130 | // Clamp hotspot coordinates between 0 and size - 1 131 | options.hotspot = $.map(options.hotspot, function(coordinate) { 132 | return Math.min(options.size - 1, Math.max(0, coordinate)); 133 | }); 134 | 135 | var cssClass = (function(name, template) { 136 | if (typeof template === 'string') { 137 | return template.replace(/%s/g, name); 138 | } else if (typeof template === 'function') { 139 | return template(name); 140 | } 141 | 142 | return name; 143 | })(iconName, options.font.cssClass), 144 | srcElement = $('', { 145 | class: cssClass, 146 | style: 'display: inline; font-size: ' + options.size + 'px;' 147 | }); 148 | 149 | // Wrap the icon inside an absolute element to remove it from doc flow 150 | var wrapper = $('
', { 151 | style: 'position: absolute; left: -9999px; top: -9999px;' 152 | }).append(srcElement); 153 | 154 | // Render element to the DOM, otherwise `getComputedStyle` will not work 155 | $('body').append(wrapper); 156 | 157 | // Get the unicode value and dimensions of the icon 158 | var unicode = window.getComputedStyle(srcElement[0], ':before') 159 | .getPropertyValue('content'), 160 | clientRect = srcElement[0].getBoundingClientRect(); 161 | 162 | var canvas = $('')[0], 163 | canvasSize = Math.max(clientRect.width, clientRect.height), 164 | hotspotOffset, dataURL, context; 165 | 166 | // Remove the source element from the DOM 167 | srcElement.remove(); 168 | 169 | // Increase the size of the canvas to account for the cursor's outline 170 | if (options.outline) { 171 | canvasSize += 2; 172 | } 173 | 174 | if (options.rotate) { 175 | 176 | // @TODO: move this into it's own function 177 | canvasSize = Math.ceil(Math.sqrt( 178 | Math.pow(canvasSize, 2) + Math.pow(canvasSize, 2) 179 | )); 180 | 181 | hotspotOffset = (canvasSize - options.size) / 2; 182 | canvas.width = canvasSize; 183 | canvas.height = canvasSize; 184 | 185 | context = canvas.getContext('2d'); 186 | context.translate(canvas.width / 2, canvas.height / 2); 187 | 188 | // Canvas API works in radians, not degrees, hence `* Math.PI / 180` 189 | context.rotate(options.rotate * Math.PI / 180); 190 | context.translate(-canvas.width / 2, -canvas.height / 2); 191 | 192 | // Translate hotspot offset 193 | options.hotspot[0] += options.hotspot[0] !== canvas.width / 2 ? 194 | hotspotOffset : 0; 195 | 196 | options.hotspot[1] += options.hotspot[1] !== canvas.height / 2 ? 197 | hotspotOffset : 0; 198 | } else { 199 | 200 | canvas.height = canvasSize; 201 | canvas.width = canvasSize; 202 | 203 | context = canvas.getContext('2d'); 204 | } 205 | 206 | /* Firefox wraps the extracted unicode value in double quotes - #10 207 | * Chrome 43+ is wrapping the extracted value in single quotes - #14 208 | */ 209 | unicode = unicode.replace(/['"]/g, ''); 210 | 211 | // Draw the cursor to the canvas 212 | context.fillStyle = options.color; 213 | context.font = options.size + 'px ' + options.font.family; 214 | context.textAlign = 'center'; 215 | context.textBaseline = 'middle'; 216 | context.fillText(unicode, canvasSize / 2, canvasSize / 2); 217 | 218 | // Check for outline option 219 | if (options.outline) { 220 | context.lineWidth = 0.5; 221 | context.strokeStyle = options.outline; 222 | context.strokeText(unicode, canvasSize / 2, canvasSize / 2); 223 | } 224 | 225 | // Check flip option 226 | if (options.flip) { 227 | canvas = flipCanvas(canvas, options.flip); 228 | } 229 | 230 | dataURL = canvas.toDataURL('image/png'); 231 | 232 | $(this) 233 | 234 | // Fixes issue with Chrome not setting cursor if already set 235 | .css('cursor', '') 236 | .css('cursor', [ 237 | 'url(' + dataURL + ')', 238 | options.hotspot[0], 239 | options.hotspot[1], 240 | ',', 241 | 'auto' 242 | ].join(' ')) 243 | ; 244 | 245 | // Maintain chaining 246 | return this; 247 | } 248 | }); 249 | 250 | // Expose the defaults so that users can override them if they want to 251 | $.fn.awesomeCursor.defaults = { 252 | color: '#000000', 253 | size: 18, 254 | hotspot: [0, 0], 255 | flip: '', 256 | rotate: 0, 257 | outline: null, 258 | font: { 259 | family: 'FontAwesome', 260 | cssClass: 'fa fa-%s' 261 | } 262 | }; 263 | }); 264 | -------------------------------------------------------------------------------- /test/.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 | "browser": true, 14 | "predef": [ 15 | "console", 16 | "jQuery", 17 | "QUnit", 18 | "module", 19 | "test", 20 | "asyncTest", 21 | "expect", 22 | "start", 23 | "stop", 24 | "ok", 25 | "equal", 26 | "notEqual", 27 | "deepEqual", 28 | "notDeepEqual", 29 | "strictEqual", 30 | "notStrictEqual", 31 | "throws" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /test/awesome-cursor-test-font/fonts/AwesomeCursorTest.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/awesome-cursor-test-font/fonts/AwesomeCursorTest.eot -------------------------------------------------------------------------------- /test/awesome-cursor-test-font/fonts/AwesomeCursorTest.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/awesome-cursor-test-font/fonts/AwesomeCursorTest.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/awesome-cursor-test-font/fonts/AwesomeCursorTest.ttf -------------------------------------------------------------------------------- /test/awesome-cursor-test-font/fonts/AwesomeCursorTest.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/awesome-cursor-test-font/fonts/AwesomeCursorTest.woff -------------------------------------------------------------------------------- /test/awesome-cursor-test-font/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'AwesomeCursorTest'; 3 | src:url('fonts/AwesomeCursorTest.eot'); 4 | src:url('fonts/AwesomeCursorTest.eot') format('embedded-opentype'), 5 | url('fonts/AwesomeCursorTest.woff') format('woff'), 6 | url('fonts/AwesomeCursorTest.ttf') format('truetype'), 7 | url('fonts/AwesomeCursorTest.svg#AwesomeCursorTest') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | .act { 13 | font-family: 'AwesomeCursorTest'; 14 | speak: none; 15 | font-style: normal; 16 | font-weight: normal; 17 | font-variant: normal; 18 | text-transform: none; 19 | line-height: 1; 20 | 21 | /* Better Font Rendering =========== */ 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | 26 | .act-pencil:before { 27 | content: "\e600"; 28 | } 29 | .act-brush:before { 30 | content: "\e601"; 31 | } 32 | -------------------------------------------------------------------------------- /test/expected/awesome-cursor-test-font/black-pencil-36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/awesome-cursor-test-font/black-pencil-36.png -------------------------------------------------------------------------------- /test/expected/awesome-cursor-test-font/green-brush-30-effects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/awesome-cursor-test-font/green-brush-30-effects.png -------------------------------------------------------------------------------- /test/expected/black-desktop-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-desktop-22.png -------------------------------------------------------------------------------- /test/expected/black-globe-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-globe-32.png -------------------------------------------------------------------------------- /test/expected/black-globe-flip-b-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-globe-flip-b-32.png -------------------------------------------------------------------------------- /test/expected/black-outline-paint-brush-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-outline-paint-brush-32.png -------------------------------------------------------------------------------- /test/expected/black-outline-paint-brush-flip-both-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-outline-paint-brush-flip-both-32.png -------------------------------------------------------------------------------- /test/expected/black-outline-paint-brush-flip-horizontal-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-outline-paint-brush-flip-horizontal-32.png -------------------------------------------------------------------------------- /test/expected/black-outline-paint-brush-flip-vertical-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-outline-paint-brush-flip-vertical-32.png -------------------------------------------------------------------------------- /test/expected/black-pencil-rotate45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-pencil-rotate45.png -------------------------------------------------------------------------------- /test/expected/blue-outline-paint-brush-rotate45-flip-h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/blue-outline-paint-brush-rotate45-flip-h.png -------------------------------------------------------------------------------- /test/expected/blue-outline-paint-brush-rotate45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/blue-outline-paint-brush-rotate45.png -------------------------------------------------------------------------------- /test/expected/green-pencil-rotate45-flip-b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/green-pencil-rotate45-flip-b.png -------------------------------------------------------------------------------- /test/expected/green-pencil-rotate45-flip-h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/green-pencil-rotate45-flip-h.png -------------------------------------------------------------------------------- /test/expected/lime-flag-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/lime-flag-18.png -------------------------------------------------------------------------------- /test/expected/lime-flag-flip-v-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/lime-flag-flip-v-18.png -------------------------------------------------------------------------------- /test/expected/pink-usb-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/pink-usb-32.png -------------------------------------------------------------------------------- /test/expected/red-pencil-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/red-pencil-18.png -------------------------------------------------------------------------------- /test/expected/red-pencil-flip-h-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/red-pencil-flip-h-18.png -------------------------------------------------------------------------------- /test/expected/red-wrench-rotate-45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/red-wrench-rotate-45.png -------------------------------------------------------------------------------- /test/jquery-awesome-cursor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jQuery Awesome Cursor plugin Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 |
26 |
27 | lame test markup 28 | normal test markup 29 | awesome test markup 30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /test/jquery-awesome-cursor_test.js: -------------------------------------------------------------------------------- 1 | /* global QUnit */ 2 | 3 | (function(global, $) { 4 | 'use strict'; 5 | 6 | QUnit.test('force fonts to load', function(assert) { 7 | assert.expect(0); 8 | 9 | $('body') 10 | .append('') 11 | .append('') 12 | ; 13 | 14 | setTimeout(assert.async(), 500); 15 | }); 16 | 17 | /* Regular expresssion which defines the expected value of the CSS cursor 18 | * property after the plugin has been called on an element 19 | */ 20 | var CURSOR_REGEX = /^url\((?:")?data:image\/png;base64,.*\)(?: 0 0)?, auto$/; 21 | 22 | /** 23 | * Extract the cursor's x, y hotspot from the specified element. 24 | * 25 | * @param {jQuery} el The element to extract the cursor's hotspot from 26 | * 27 | * @return {Array|undefined} an array containing the cursor's x and y values, 28 | * or `undefined` if extraction fails 29 | */ 30 | function extractHotspot(el) { 31 | var match = el.attr('style') 32 | .match(/^cursor: url\(.*\) ([0-9]+) ([0-9]+), auto;$/); 33 | 34 | if (match && match.length === 3) { 35 | return match.slice(1).map(function(val) { 36 | return parseFloat(val); 37 | }); 38 | } 39 | 40 | return; 41 | } 42 | 43 | /** 44 | * Get a canvas whose contents represent the supplied image. 45 | * 46 | * @param {Image} img The image to render to the canvas 47 | * 48 | * @return {Canvas} the new canvas element, with `img` rendered to it 49 | */ 50 | function getCanvasFromImage(img) { 51 | 52 | // Create an empty canvas element 53 | var canvas = document.createElement('canvas'); 54 | canvas.width = img.width; 55 | canvas.height = img.height; 56 | 57 | // Copy the image contents to the canvas 58 | var ctx = canvas.getContext('2d'); 59 | ctx.drawImage(img, 0, 0); 60 | 61 | return canvas; 62 | } 63 | 64 | /** 65 | * Get a canvas with the supplied data URI rendered to it. 66 | * 67 | * @param {String} uri The data URI 68 | * 69 | * @return {Canvas} a new canvas element, with the supplied data rendered to 70 | * it 71 | */ 72 | function getCanvasFromDataURI(uri) { 73 | return getCanvasFromImage($('', { 74 | src: uri 75 | })[0]); 76 | } 77 | 78 | /** 79 | * Compare the content of two canvasses, by checking each pixel in the first 80 | * canvas against the second canvas. Bails early and returns false if: 81 | * 82 | * - the two canvasses have different widths or heights 83 | * - the length of the canvasses data attribute is different 84 | * 85 | * @return {Boolean} true if every single pixel in `canvas1` matches every 86 | * pixel in `canvas2` 87 | */ 88 | function canvasCompare(canvas1, canvas2) { 89 | if (!(canvas1.width === canvas2.width && 90 | canvas1.height === canvas2.height)) { 91 | return false; 92 | } 93 | 94 | var ctx1 = canvas1.getContext('2d'), 95 | ctx2 = canvas2.getContext('2d'), 96 | data1 = ctx1.getImageData(0, 0, canvas1.width, canvas1.height), 97 | data2 = ctx2.getImageData(0, 0, canvas2.width, canvas2.height); 98 | 99 | if (data1.data.length !== data2.data.length) { 100 | return false; 101 | } 102 | 103 | for (var d = 0; d < data1.data.length; d++) { 104 | if (data1.data[d] !== data2.data[d]) { 105 | 106 | return false; 107 | 108 | } 109 | } 110 | 111 | return true; 112 | } 113 | 114 | /** 115 | * Check if the supplied image matches the cursor set on this element. 116 | * If element is not provided, it defaults to the body element. 117 | * 118 | * @param {String} imgSrc The src URL for the image to check 119 | * @param {Function} callback Gets called with true if the cursor matches the 120 | * image, or false if it doesn't. The 2nd and 3rd 121 | * parameters to the callback function are the 122 | * cursor's image data and the image data for 123 | * `imgSrc` respectively 124 | */ 125 | $.fn.cursorMatchesImage = function(imgSrc, callback) { 126 | callback = typeof callback === 'function' ? callback : $.noop; 127 | 128 | var actualImgData = this.css('cursor').match(/^url\((.*)\)/), 129 | $expectedImg; 130 | 131 | if (!actualImgData || actualImgData.length !== 2) { 132 | return callback(false); 133 | } 134 | 135 | $expectedImg = $('', { 136 | src: imgSrc 137 | }); 138 | 139 | $expectedImg.load(function() { 140 | var expectedCanvas = getCanvasFromImage($expectedImg[0]), 141 | actualCanvas = getCanvasFromDataURI(actualImgData[1].replace(/^["']|["']$/g, '')); 142 | 143 | if (!canvasCompare(expectedCanvas, actualCanvas)) { 144 | $('body').append(actualCanvas, expectedCanvas, '
' + imgSrc + '
'); 145 | 146 | return callback(false); 147 | } 148 | 149 | return callback(true); 150 | }); 151 | }; 152 | 153 | QUnit.module('jQuery#awesomeCursor', { 154 | 155 | // This will run before each test in this module. 156 | beforeEach: function() { 157 | $('#qunit-fixture').remove(); 158 | 159 | var $qunit = $('
', { 160 | id: 'qunit' 161 | }); 162 | var $fixture = $('
', { 163 | id: 'qunit-fixture' 164 | }); 165 | 166 | $fixture 167 | .append($('', { 168 | class: 'fa fa-pencil' 169 | })) 170 | .append($('', { 171 | class: 'act act-pencil' 172 | })) 173 | ; 174 | 175 | $('body').append($qunit).append($fixture); 176 | this.elems = $('#qunit-fixture').children(); 177 | } 178 | }); 179 | 180 | QUnit.test('is chainable', function(assert) { 181 | assert.expect(1); 182 | 183 | assert.strictEqual( 184 | this.elems.awesomeCursor('pencil'), this.elems, 'should be chainable' 185 | ); 186 | }); 187 | 188 | QUnit.test('css is correctly set', function(assert) { 189 | assert.expect(1); 190 | this.elems.awesomeCursor('pencil'); 191 | 192 | assert.ok( 193 | CURSOR_REGEX.test(this.elems.css('cursor')), 194 | '\'' + this.elems.css('cursor') + '\' does not match expected RegExp' 195 | ); 196 | }); 197 | 198 | QUnit.test('throws an error if name parameter is missing or invalid', function(assert) { 199 | var subjects = ['', 0, -1, false, true, {}, [], null], 200 | that = this; 201 | 202 | assert.expect(subjects.length); 203 | 204 | subjects.forEach(function(subject) { 205 | assert.throws( 206 | function() { 207 | that.elems.awesomeCursor(subject); 208 | }, 209 | 'Expected an error to be thrown when using `' + subject + 210 | '` as icon name parameter'); 211 | }); 212 | }); 213 | 214 | QUnit.test('`hotspot` value can be a string', function(assert) { 215 | assert.expect(1); 216 | 217 | assert.ok(this.elems.awesomeCursor('pencil', { 218 | hotspot: 'bottom left' 219 | })); 220 | }); 221 | 222 | QUnit.test('`hotspot` string values are correctly parsed', function(assert) { 223 | var size = $.fn.awesomeCursor.defaults.size, 224 | subjects = { 225 | 'top left': [0, 0], 226 | 'center top': [size / 2, 0], 227 | 'top right': [size - 1, 0], 228 | 'center left': [0, size / 2], 229 | 'center': [size / 2, size / 2], 230 | 'center right': [size - 1, size / 2], 231 | 'bottom left': [0, size - 1], 232 | 'center bottom': [size / 2, size - 1], 233 | 'bottom right': [size - 1, size - 1] 234 | }, 235 | hotspot; 236 | 237 | assert.expect(Object.keys(subjects).length); 238 | 239 | for (var s in subjects) { 240 | this.elems.awesomeCursor('pencil', { 241 | hotspot: s 242 | }); 243 | 244 | hotspot = extractHotspot(this.elems); 245 | assert.deepEqual(hotspot, subjects[s]); 246 | } 247 | }); 248 | 249 | QUnit.test('`hotspot` values get clamped between 0 and cursor size - 1', function(assert) { 250 | var hotspot; 251 | 252 | assert.expect(3); 253 | 254 | this.elems.awesomeCursor('pencil', { 255 | size: 32, 256 | hotspot: [-3, 34] 257 | }); 258 | 259 | hotspot = extractHotspot(this.elems); 260 | 261 | assert.ok(hotspot); 262 | assert.equal(hotspot[0], 0); 263 | assert.equal(hotspot[1], 31); 264 | }); 265 | 266 | QUnit.test('can set the color of a cursor', function(assert) { 267 | var done = assert.async(); 268 | 269 | assert.expect(2); 270 | 271 | var next = function() { 272 | this.elems 273 | .awesomeCursor('flag-checkered', { 274 | color: '#00ff00', 275 | size: 18 276 | }) 277 | .cursorMatchesImage( 278 | 'expected/lime-flag-18.png', function(matches) { 279 | assert.ok(matches); 280 | done(); 281 | }); 282 | }.bind(this); 283 | 284 | this.elems 285 | .awesomeCursor('pencil', { 286 | color: 'red', 287 | size: 18 288 | }) 289 | .cursorMatchesImage( 290 | 'expected/red-pencil-18.png', function(matches) { 291 | assert.ok(matches); 292 | next(); 293 | }); 294 | 295 | }); 296 | 297 | QUnit.test('can set the size of a cursor', function(assert) { 298 | var done = assert.async(); 299 | assert.expect(1); 300 | 301 | this.elems 302 | .awesomeCursor('globe', { 303 | color: 'black', 304 | size: 32 305 | }) 306 | .cursorMatchesImage( 307 | 'expected/black-globe-32.png', function(matches) { 308 | assert.ok(matches); 309 | done(); 310 | }); 311 | }); 312 | 313 | QUnit.test('can set the size of a cursor using a string value', function(assert) { 314 | var done = assert.async(); 315 | assert.expect(1); 316 | 317 | this.elems 318 | .awesomeCursor('desktop', { 319 | color: 'black', 320 | size: '22px' 321 | }) 322 | .cursorMatchesImage( 323 | 'expected/black-desktop-22.png', function(matches) { 324 | assert.ok(matches); 325 | done(); 326 | }); 327 | }); 328 | 329 | QUnit.test('can flip a cursor horizontally', function(assert) { 330 | var done = assert.async(); 331 | assert.expect(1); 332 | 333 | this.elems 334 | .awesomeCursor('pencil', { 335 | color: 'red', 336 | size: 18, 337 | flip: 'horizontal' 338 | }) 339 | .cursorMatchesImage( 340 | 'expected/red-pencil-flip-h-18.png', function(matches) { 341 | assert.ok(matches); 342 | done(); 343 | }); 344 | }); 345 | 346 | QUnit.test('can flip a cursor vertically', function(assert) { 347 | var done = assert.async(); 348 | assert.expect(1); 349 | 350 | this.elems 351 | .awesomeCursor('flag-checkered', { 352 | color: '#00ff00', 353 | size: 18, 354 | flip: 'vertical' 355 | }) 356 | .cursorMatchesImage( 357 | 'expected/lime-flag-flip-v-18.png', function(matches) { 358 | assert.ok(matches); 359 | done(); 360 | }); 361 | }); 362 | 363 | QUnit.test('can flip a cursor vertically and horizontally', function(assert) { 364 | var done = assert.async(); 365 | assert.expect(1); 366 | 367 | this.elems 368 | .awesomeCursor('globe', { 369 | color: 'black', 370 | size: 32, 371 | flip: 'both' 372 | }) 373 | .cursorMatchesImage( 374 | 'expected/black-globe-flip-b-32.png', function(matches) { 375 | assert.ok(matches); 376 | done(); 377 | }); 378 | }); 379 | 380 | QUnit.test('can rotate a cursor', function(assert) { 381 | var done = assert.async(); 382 | assert.expect(2); 383 | 384 | var next = function() { 385 | this.elems.awesomeCursor('wrench', { 386 | color: 'red', 387 | size: '32px', 388 | rotate: -45 389 | }).cursorMatchesImage( 390 | 'expected/red-wrench-rotate-45.png', function(matches) { 391 | assert.ok(matches); 392 | done(); 393 | } 394 | ); 395 | }.bind(this); 396 | 397 | this.elems.awesomeCursor('pencil', { 398 | color: 'black', 399 | size: '32px', 400 | rotate: 45 401 | }).cursorMatchesImage( 402 | 'expected/black-pencil-rotate45.png', function(matches) { 403 | assert.ok(matches); 404 | next(); 405 | } 406 | ); 407 | 408 | }); 409 | 410 | QUnit.test('can rotate and flip a cursor', function(assert) { 411 | var done = assert.async(); 412 | assert.expect(2); 413 | 414 | var next = function() { 415 | this.elems.awesomeCursor('pencil', { 416 | color: 'green', 417 | size: '32px', 418 | rotate: 45, 419 | flip: 'both' 420 | }).cursorMatchesImage( 421 | 'expected/green-pencil-rotate45-flip-b.png', function(matches) { 422 | assert.ok(matches); 423 | done(); 424 | } 425 | ); 426 | }.bind(this); 427 | 428 | this.elems.awesomeCursor('pencil', { 429 | color: 'green', 430 | size: '32px', 431 | rotate: 45, 432 | flip: 'horizontal' 433 | }).cursorMatchesImage( 434 | 'expected/green-pencil-rotate45-flip-h.png', function(matches) { 435 | assert.ok(matches); 436 | next(); 437 | } 438 | ); 439 | 440 | }); 441 | 442 | QUnit.test('hotspot gets translated when cursor rotated', function(assert) { 443 | var size = $.fn.awesomeCursor.defaults.size, 444 | newSize = Math.ceil(Math.sqrt( 445 | Math.pow(size, 2) + Math.pow(size, 2) 446 | )), 447 | subjects = { 448 | 45: [(newSize - size) / 2, (size / 2) + (newSize - size) / 2] 449 | }, 450 | hotspot; 451 | 452 | assert.expect(Object.keys(subjects).length); 453 | 454 | for (var s in subjects) { 455 | this.elems.awesomeCursor('pencil', { 456 | hotspot: 'center left', 457 | rotate: s 458 | }); 459 | 460 | hotspot = extractHotspot(this.elems); 461 | assert.deepEqual(hotspot, subjects[s]); 462 | } 463 | }); 464 | 465 | QUnit.test('can add outline to cursor', function(assert) { 466 | var done = assert.async(); 467 | assert.expect(1); 468 | 469 | this.elems.awesomeCursor('paint-brush', { 470 | color: 'white', 471 | size: 32, 472 | outline: 'black' 473 | }).cursorMatchesImage( 474 | 'expected/black-outline-paint-brush-32.png', function(matches) { 475 | assert.ok(matches); 476 | done(); 477 | } 478 | ); 479 | }); 480 | 481 | QUnit.test('can add outlines to flipped cursors', function(assert) { 482 | var done = assert.async(); 483 | assert.expect(3); 484 | 485 | var runTests = function(tests) { 486 | var current = tests.pop(); 487 | 488 | if (!current) { 489 | done(); 490 | } else { 491 | 492 | this.elems.awesomeCursor('paint-brush', { 493 | color: 'white', 494 | size: 32, 495 | outline: 'black', 496 | flip: current 497 | }).cursorMatchesImage( 498 | 'expected/black-outline-paint-brush-flip-' + current + '-32.png', 499 | function(matches) { 500 | assert.ok(matches); 501 | runTests(tests); 502 | } 503 | ); 504 | } 505 | }.bind(this); 506 | 507 | runTests(['horizontal', 'vertical', 'both']); 508 | 509 | }); 510 | 511 | QUnit.test('can add outline to rotated cursor', function(assert) { 512 | var done = assert.async(); 513 | assert.expect(1); 514 | 515 | this.elems.awesomeCursor('pencil', { 516 | color: 'skyblue', 517 | size: 32, 518 | rotate: 45, 519 | outline: 'blue' 520 | }).cursorMatchesImage( 521 | 'expected/blue-outline-paint-brush-rotate45.png', function(matches) { 522 | assert.ok(matches); 523 | done(); 524 | } 525 | ); 526 | }); 527 | 528 | QUnit.test('can add outline to rotated and flipped cursor', function(assert) { 529 | var done = assert.async(); 530 | assert.expect(1); 531 | 532 | this.elems.awesomeCursor('pencil', { 533 | color: 'skyblue', 534 | size: 32, 535 | rotate: 45, 536 | outline: 'blue', 537 | flip: 'horizontal' 538 | }).cursorMatchesImage( 539 | 'expected/blue-outline-paint-brush-rotate45-flip-h.png', function(matches) { 540 | assert.ok(matches); 541 | done(); 542 | } 543 | ); 544 | }); 545 | 546 | QUnit.test('can use a custom font instead of FontAwesome', function(assert) { 547 | var done = assert.async(); 548 | assert.expect(1); 549 | 550 | this.elems.awesomeCursor('pencil', { 551 | font: { 552 | family: 'AwesomeCursorTest', 553 | cssClass: 'act act-%s' 554 | }, 555 | size: 36, 556 | color: 'black' 557 | }).cursorMatchesImage( 558 | 'expected/awesome-cursor-test-font/black-pencil-36.png', function(matches) { 559 | assert.ok(matches); 560 | done(); 561 | } 562 | ); 563 | }); 564 | 565 | QUnit.test('can apply effects to custom font cursors', function(assert) { 566 | var done = assert.async(); 567 | assert.expect(1); 568 | 569 | this.elems.awesomeCursor('brush', { 570 | font: { 571 | family: 'AwesomeCursorTest', 572 | cssClass: 'act act-%s' 573 | }, 574 | size: 30, 575 | color: 'limegreen', 576 | outline: 'forestgreen', 577 | rotate: 35, 578 | flip: 'horizontal' 579 | }).cursorMatchesImage( 580 | 'expected/awesome-cursor-test-font/green-brush-30-effects.png', function(matches) { 581 | assert.ok(matches); 582 | done(); 583 | } 584 | ); 585 | }); 586 | 587 | QUnit.test('can set custom font `cssClass` using a function', function(assert) { 588 | var done = assert.async(); 589 | assert.expect(1); 590 | 591 | this.elems.awesomeCursor('pencil', { 592 | font: { 593 | family: 'AwesomeCursorTest', 594 | cssClass: function(iconName) { 595 | return 'act act-' + iconName; 596 | } 597 | }, 598 | size: 36, 599 | color: 'black' 600 | }).cursorMatchesImage( 601 | 'expected/awesome-cursor-test-font/black-pencil-36.png', function(matches) { 602 | assert.ok(matches); 603 | done(); 604 | } 605 | ); 606 | }); 607 | 608 | QUnit.test('does not clip large icons', function(assert) { 609 | var done = assert.async(); 610 | assert.expect(1); 611 | 612 | this.elems.awesomeCursor('usb', { 613 | size: 32, 614 | color: 'pink' 615 | }).cursorMatchesImage( 616 | 'expected/pink-usb-32.png', function(matches) { 617 | assert.ok(matches); 618 | done(); 619 | } 620 | ); 621 | }); 622 | }(this, jQuery)); 623 | --------------------------------------------------------------------------------