├── .editorconfig ├── .gitignore ├── .jshintrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gruntfile.js ├── README.md ├── component.json ├── docs ├── API.md ├── README.md └── Styling.md ├── index.js ├── lib └── tduration.js ├── meta.json ├── package.json ├── test ├── index.html ├── test.css └── test.js ├── tooltip.css ├── transitions.css └── types.css /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | components 2 | node_modules 3 | build 4 | dist -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef" : [ 3 | "module", 4 | "require" 5 | ], 6 | 7 | "bitwise": false, 8 | "camelcase": false, 9 | "curly": true, 10 | "eqeqeq": true, 11 | "forin": false, 12 | "immed": true, 13 | "latedef": false, 14 | "newcap": true, 15 | "noarg": true, 16 | "noempty": true, 17 | "nonew": false, 18 | "plusplus": false, 19 | "quotmark": false, 20 | "regexp": false, 21 | "undef": true, 22 | "unused": true, 23 | "strict": true, 24 | "trailing": true, 25 | 26 | "asi": false, 27 | "boss": false, 28 | "debug": false, 29 | "eqnull": true, 30 | "es5": false, 31 | "esnext": false, 32 | "evil": false, 33 | "expr": false, 34 | "funcscope": false, 35 | "globalstrict": true, 36 | "iterator": false, 37 | "lastsemic": false, 38 | "laxbreak": false, 39 | "laxcomma": true, 40 | "loopfunc": false, 41 | "multistr": false, 42 | "onecase": true, 43 | "proto": false, 44 | "regexdash": false, 45 | "scripturl": false, 46 | "smarttabs": true, 47 | "shadow": false, 48 | "sub": false, 49 | "supernew": false, 50 | 51 | "browser": true 52 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | **0.1.0** :: *18th Dec 2013* 4 | 5 | - Initial release. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Submitting an issue 2 | 3 | When reporting a bug, please describe it thoroughly, with attached code showcasing how are you using this library. The 4 | best way how to make it easy for developers, and ensure that your issue will be looked at, is to replicate it on 5 | [jsfiddle](http://jsfiddle.net/) or a similar service. 6 | 7 | ## Contributions 8 | 9 | Contributions are welcome! But please, follow these few simple rules: 10 | 11 | **Maintain the coding style** used throughout the project, and defined in the `.editorconfig` file. You can use the 12 | [Editorconfig](http://editorconfig.org) plugin for your editor of choice: 13 | 14 | - [Sublime Text 2](https://github.com/sindresorhus/editorconfig-sublime) 15 | - [Textmate](https://github.com/Mr0grog/editorconfig-textmate) 16 | - [Notepad++](https://github.com/editorconfig/editorconfig-notepad-plus-plus) 17 | - [Emacs](https://github.com/editorconfig/editorconfig-emacs) 18 | - [Vim](https://github.com/editorconfig/editorconfig-vim) 19 | - [Visual Studio](https://github.com/editorconfig/editorconfig-visualstudio) 20 | - [... other editors](http://editorconfig.org/#download) 21 | 22 | --- 23 | 24 | **Code has to pass JSHint** with options defined in the `.jshintrc` file. You can use `grunt jshint` task to lint 25 | manually, or again, there are amazing plugins for a lot of popular editors consuming this file and linting as you code: 26 | 27 | - [Sublim Text 2](https://github.com/SublimeLinter/SublimeLinter) 28 | - [TextMate](http://rondevera.github.com/jslintmate/), or [alternative](http://fgnass.posterous.com/jslint-in-textmate) 29 | - [Notepad++](http://sourceforge.net/projects/jslintnpp/) 30 | - [Emacs](https://github.com/daleharvey/jshint-mode) 31 | - [Vim](https://github.com/walm/jshint.vim) 32 | - [Visual Studio](https://github.com/jamietre/SharpLinter), or [alternative](http://jslint4vs2010.codeplex.com/) 33 | - [... other editors](http://www.jshint.com/platforms/) -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | module.exports = function(grunt) { 3 | 'use strict'; 4 | 5 | // Override environment based line endings enforced by Grunt 6 | grunt.util.linefeed = '\n'; 7 | 8 | // Grunt configuration 9 | grunt.initConfig({ 10 | pkg: grunt.file.readJSON('meta.json'), 11 | meta: { 12 | banner: '/*!\n' + 13 | ' * <%= pkg.name %> <%= pkg.version %> - <%= grunt.template.today("dS mmm yyyy") %>\n' + 14 | ' * <%= pkg.repository.homepage %>\n' + 15 | ' *\n' + 16 | ' * Licensed under the <%= pkg.licenses[0].type %> license.\n' + 17 | ' * <%= pkg.licenses[0].url %>\n' + 18 | ' */\n', 19 | bannerLight: '/*! <%= pkg.name %> <%= pkg.version %>' + 20 | ' - <%= grunt.template.today("dS mmm yyyy") %> | <%= pkg.repository.homepage %> */\n', 21 | }, 22 | 23 | // JSHint the code. 24 | jshint: { 25 | options: { 26 | jshintrc: true, 27 | }, 28 | all: ['index.js'], 29 | }, 30 | 31 | // Clean folders. 32 | clean: { 33 | build: ['build/**'], 34 | tmp: ['tmp/**'], 35 | dist: ['dist/**'] 36 | }, 37 | 38 | // Concatenate files. 39 | concat: { 40 | development: { 41 | options: { 42 | banner: '<%= meta.banner %>' 43 | }, 44 | src: 'tmp/<%= pkg.name %>.js', 45 | dest: 'tmp/<%= pkg.name %>.js', 46 | }, 47 | style: { 48 | options: { 49 | banner: '<%= meta.bannerLight %>' 50 | }, 51 | src: 'tmp/<%= pkg.name %>.css', 52 | dest: 'tmp/<%= pkg.name %>.css', 53 | }, 54 | }, 55 | 56 | // Minify files. 57 | uglify: { 58 | production: { 59 | options: { 60 | banner: '<%= meta.bannerLight %>', 61 | report: 'gzip', 62 | }, 63 | src: 'tmp/<%= pkg.name %>.js', 64 | dest: 'tmp/<%= pkg.name %>.min.js' 65 | }, 66 | }, 67 | 68 | // Copy files. 69 | copy: { 70 | dist: { 71 | expand: true, 72 | cwd: 'tmp', 73 | src: ['*.js', '*.css'], 74 | dest: 'dist/', 75 | }, 76 | }, 77 | 78 | // Build components. 79 | componentbuild: { 80 | test: { 81 | options: { 82 | dev: true, 83 | sourceUrls: true, 84 | }, 85 | src: '.', 86 | dest: 'build', 87 | }, 88 | production: { 89 | options: { 90 | name: '<%= pkg.name %>', 91 | standalone: 'Tooltip', 92 | }, 93 | src: '.', 94 | dest: 'tmp', 95 | }, 96 | development: { 97 | options: { 98 | name: '<%= pkg.name %>', 99 | standalone: 'Tooltip', 100 | sourceUrls: true, 101 | }, 102 | src: '.', 103 | dest: 'tmp', 104 | }, 105 | }, 106 | 107 | // Create zipfiles. 108 | compress: { 109 | options: { 110 | level: 9, 111 | pretty: true, 112 | }, 113 | standalone: { 114 | options: { 115 | archive: 'dist/tooltip.zip', 116 | }, 117 | expand: true, 118 | cwd: 'tmp', 119 | src: ['*'], 120 | }, 121 | }, 122 | 123 | // Watch for changes and run tasks. 124 | watch: { 125 | component: { 126 | files: ['index.js', '*.css'], 127 | tasks: ['componentbuild:test'], 128 | options: { 129 | spawn: false, 130 | livereload: true, 131 | }, 132 | }, 133 | test: { 134 | files: ['test/*.js', 'test/*.css'], 135 | options: { 136 | spawn: false, 137 | livereload: true, 138 | }, 139 | }, 140 | }, 141 | 142 | // Bump up fields in JSON files. 143 | bumpup: { 144 | options: { 145 | updateProps: { 146 | pkg: 'meta.json', 147 | }, 148 | }, 149 | files: ['meta.json', 'component.json'], 150 | }, 151 | 152 | // Commit changes and tag the latest commit with a version from JSON file. 153 | tagrelease: '<%= pkg.version %>' 154 | }); 155 | 156 | // These plugins provide necessary tasks. 157 | grunt.loadNpmTasks('grunt-contrib-compress'); 158 | grunt.loadNpmTasks('grunt-contrib-uglify'); 159 | grunt.loadNpmTasks('grunt-contrib-jshint'); 160 | grunt.loadNpmTasks('grunt-contrib-concat'); 161 | grunt.loadNpmTasks('grunt-contrib-clean'); 162 | grunt.loadNpmTasks('grunt-contrib-watch'); 163 | grunt.loadNpmTasks('grunt-contrib-copy'); 164 | grunt.loadNpmTasks('grunt-component-build'); 165 | grunt.loadNpmTasks('grunt-tagrelease'); 166 | grunt.loadNpmTasks('grunt-bumpup'); 167 | 168 | // Dev task. 169 | grunt.registerTask('dev', function () { 170 | grunt.task.run('componentbuild:test'); 171 | }); 172 | 173 | // Build task. 174 | grunt.registerTask('dist', function () { 175 | grunt.task.run('jshint'); 176 | grunt.task.run('clean:tmp'); 177 | grunt.task.run('clean:dist'); 178 | // Production 179 | grunt.task.run('componentbuild:production'); 180 | grunt.task.run('uglify:production'); 181 | // Development 182 | grunt.task.run('componentbuild:development'); 183 | grunt.task.run('concat:development'); 184 | // Distribution 185 | grunt.task.run('concat:style'); 186 | grunt.task.run('copy:dist'); 187 | grunt.task.run('compress'); 188 | // Cleanup 189 | grunt.task.run('clean:tmp'); 190 | }); 191 | 192 | // Release task. 193 | grunt.registerTask('release', function (type) { 194 | type = type ? type : 'patch'; 195 | grunt.task.run('jshint'); 196 | grunt.task.run('bumpup:' + type); 197 | grunt.task.run('tagrelease'); 198 | }); 199 | 200 | // Default task. 201 | grunt.registerTask('default', ['jshint']); 202 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Tooltip](http://darsa.in/tooltip) 2 | 3 | JavaScript library for basic tooltip implementation. 4 | 5 | Tooltip doesn't provide bindings to user interaction (like displaying on hover). It is designed to be consumed by 6 | wrappers like [`darsain/tooltips`](https://github.com/darsain/tooltips), form validation libraries, etc. 7 | 8 | Supports typed classes, and effects via seamless CSS transitions (you don't have to define transition durations in JavaScript). 9 | 10 | #### Support 11 | 12 | Starts at IE8, but Tooltip arrows are styled for IE9+. If you care, just restyle the 13 | `.tooltip:after` arrow with IE8 in mind. 14 | 15 | Other browsers just work. 16 | 17 | ## Install 18 | 19 | [Component](https://github.com/component/component): 20 | 21 | ```bash 22 | component install darsain/tooltip 23 | ``` 24 | 25 | ## Download 26 | 27 | Standalone build of a latest stable version: 28 | 29 | - [`tooltip.zip`](http://darsain.github.io/tooltip/dist/tooltip.zip) - combined archive 30 | - [`tooltip.js`](http://darsain.github.io/tooltip/dist/tooltip.js) - 25 KB *sourcemapped* 31 | - [`tooltip.min.js`](http://darsain.github.io/tooltip/dist/tooltip.min.js) - 10 KB, 2KB gzipped 32 | - [`tooltip.css`](http://darsain.github.io/tooltip/dist/tooltip.css) - 4.5 KB *including transitions & types* 33 | 34 | When isolating issues on jsfiddle, use the [`tooltip.js`](http://darsain.github.io/tooltip/dist/tooltip.js) URL above. 35 | 36 | ## Documentation 37 | 38 | Can be found in the **[docs](docs)** directory. 39 | 40 | ## License 41 | 42 | MIT -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tooltip", 3 | "repo": "darsain/tooltip", 4 | "description": "Tooltip component.", 5 | "version": "0.1.0", 6 | "keywords": [ 7 | "tooltip", 8 | "tip", 9 | "ui" 10 | ], 11 | "dependencies": { 12 | "darsain/fuse": "^1.0.0", 13 | "darsain/raft": "^1.0.0", 14 | "darsain/event": "^1.0.0", 15 | "darsain/extend": "^1.0.0", 16 | "darsain/position": "^1.0.0", 17 | "darsain/parse-number": "^1.0.0", 18 | "component/classes": "^1.0.0", 19 | "component/indexof": "*", 20 | "jkroso/computed-style": "*" 21 | }, 22 | "development": { 23 | "components/jquery": "1.10.2" 24 | }, 25 | "scripts": [ 26 | "index.js", 27 | "lib/tduration.js" 28 | ], 29 | "styles": [ 30 | "tooltip.css", 31 | "transitions.css", 32 | "types.css" 33 | ], 34 | "license": "MIT" 35 | } -------------------------------------------------------------------------------- /docs/API.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | ## Tooltip(content, [options]); 4 | 5 | 6 | ### content 7 | 8 | Type: `String|Element` 9 | 10 | Tooltip content. Can be a text, an HTML string, or a DOM element. Will be used as `innerHTML` when a string, and `.appendChild()` when an element. 11 | 12 | If you are inserting image elements, they need to have their dimensions defined (either with `width` & `height` attributes, or in CSS), otherwise the Tooltip will work with dimensions before the image load. You can also bind a load event to the image, and when it triggers call `.updateSize()` method. 13 | 14 | ### [options] 15 | 16 | Type: `Object` 17 | 18 | Tooltip options object. Default options are stored in `Tooltip.defaults`. 19 | 20 | ```js 21 | Tooltip.defaults = { 22 | baseClass: 'tooltip', // Base Tooltip class name. 23 | typeClass: null, // Type Tooltip class name. 24 | effectClass: null, // Effect Tooltip class name. 25 | inClass: 'in', // Class used to transition stuff in. 26 | place: 'top', // Default place. 27 | spacing: null, // Gap between target and Tooltip. 28 | auto: 0 // Whether to automatically adjust place to fit into window. 29 | }; 30 | ``` 31 | 32 | ##### baseClass 33 | 34 | Type `String` Default `tooltip` 35 | 36 | Base Tooltip class that will be applied to Tooltip element upon creation. This class should define Tooltip's layout & dimensions, and shouldn't be changed. 37 | 38 | ##### typeClass 39 | 40 | Type `String` Default `null` 41 | 42 | Tooltip's type class that will be applied to Tooltip element upon creation. This class should define only visual changes that don't affect layout & dimensions. 43 | 44 | Can be changed dynamically with `#type()` method. 45 | 46 | ##### effectClass 47 | 48 | Type `String` Default `null` 49 | 50 | Tooltip's effect class that will be applied to Tooltip element upon creation. This class - in a correlation with **inClass** - should define only Tooltip transitions. Example fade transition: 51 | 52 | ```css 53 | .tooltip.fade { opacity: 0; transition: opacity 200ms ease-out; } 54 | .tooltip.fade.in { opacity: 1; transition-duration: 100ms; } 55 | ``` 56 | 57 | Can be changed dynamically with `#effect()` method. 58 | 59 | ##### inClass 60 | 61 | Type `String` Default `in` 62 | 63 | Tooltip's class for "transitioning in" state. It is applied to Tooltip when it is being shown. This class doesn't have to declare anything, it functions mostly as a helper selector for effect classes. 64 | 65 | ##### place 66 | 67 | Type `String` Default `top` 68 | 69 | This options specifies Tooltip's growth direction. Tooltip supports this places: 70 | 71 | - `top`, `top-left`, `top right` 72 | - `bottom`, `bottom-left`, `bottom-right` 73 | - `left`, `left-top`, `left-bottom` 74 | - `right`, `right-top`, `right-bottom` 75 | 76 | ##### spacing 77 | 78 | Type `Integer` Default `null` 79 | 80 | Defines the gap between Tooltip and target position, used mainly to properly display Tooltip arrow. This gap is defined with a `top` property in Tooltip's base class: 81 | 82 | ```css 83 | .tooltip { 84 | top: 10px; /* Defines the spacing between Tooltip and target position */ 85 | } 86 | ``` 87 | 88 | But can be overridden with this option. 89 | 90 | ##### auto 91 | 92 | Type `Boolean` Default `false` 93 | 94 | When set to `true`, Tooltip will check whether the current requested place is possible to display within window borders, and if not, it'll try to pick a better one. 95 | 96 | Unless stated otherwise, all methods return Tooltip object, making them chainable. 97 | 98 | ## #content(content) 99 | 100 | Change Tooltip's content. If Tooltip is visible, it'll automatically readjust it's dimensions and position. 101 | 102 | - **content** `String|Element` Can be a text, escaped HTML string, or DOM element. 103 | 104 | ## #position(x, y) 105 | 106 | Position the Tooltip on a specific coordinates. 107 | 108 | ## #position(element) 109 | 110 | Position the Tooltip while using an element as position target. 111 | 112 | ## #show([element / x, y]) 113 | 114 | Show the Tooltip, and optionally position it. When arguments are present, they are directly relayed to `#position()` method. 115 | 116 | ## #hide() 117 | 118 | Hide Tooltip. 119 | 120 | ## #toggle() 121 | 122 | Hide when shown, show when hidden. 123 | 124 | ## #attach(element) 125 | 126 | Attach Tooltip to DOM element. When Tooltip is attached, `#position()` arguments are ignored and attached element is always used as a Tooltip target position. When attached Tooltip is visible, it is automatically repositioned on window resize. 127 | 128 | ## #detach() 129 | 130 | Detach Tooltip from element. 131 | 132 | ## #type(name) 133 | 134 | Change type class. Pass falsy or no value to remove type class. 135 | 136 | ## #effect(name) 137 | 138 | Change effect class. Pass falsy or no value to remove effect class. 139 | 140 | ## #changeClassType(type, name) 141 | 142 | The underlying method wrapped by `#type()` & `#effect()`. For example, the `#type()` method is basically just a: 143 | 144 | ```js 145 | Tooltip.prototype.type = function (name) { 146 | return this.changeClassType('type', name); 147 | }; 148 | ``` 149 | 150 | You can use this to create your own class types. 151 | 152 | ## #place(name) 153 | 154 | Change the Tooltip place. When Tooltip is visible, it is automatically repositioned to the requested place. 155 | 156 | Supported places are: 157 | 158 | - `top`, `top-left`, `top-right` 159 | - `bottom`, `bottom-left`, `bottom-right` 160 | - `left`, `left-top`, `left-bottom` 161 | - `right`, `right-top`, `right-bottom` 162 | 163 | ## #updateSize() 164 | 165 | Updates `#width` & `#height` properties to be in sync with the actual size of a Tooltip element. It's run on Tooltip creation and automatically on `#content()` changes. 166 | 167 | You'd want to use this when you are inserting a DOM element as a Tooltip content, and than manipulating this element from outside without re-applying it via `#content()`. Whenever some layout change happens to that element, `#updateSize()` should be called. 168 | 169 | In other words, if you change the Tooltip content without using `#content()`, call this afterwards. 170 | 171 | ## #element 172 | 173 | Tooltip element. This element is detached from DOM when Tooltip is hidden. 174 | 175 | ## #classes 176 | 177 | A [`component/classes`](https://github.com/component/classes) object handling Tooltip element classes. You can do: 178 | 179 | ```js 180 | tip.classes.add('foo'); 181 | tip.classes.remove('foo'); 182 | tip.classes.toggle('foo'); 183 | tip.classes.has('foo'); 184 | tip.classes.array(); // returns an array of all current classes 185 | ``` 186 | 187 | ## #hidden 188 | 189 | Type `Boolean` 190 | 191 | True when Tooltip is hidden, false otherwise. 192 | 193 | ## #options 194 | 195 | Tooltip options object. 196 | 197 | ## #spacing 198 | 199 | Type `Integer` 200 | 201 | Tooltip spacing - the additional gap between Tooltip and it's target position. It is used to create a gap between Tooltip and its target position so the Tooltip arrow would not overlap the target. This value is a mirror of `#options.spacing` when defined, or retrieved from `baseClass`'s `top` style otherwise. For example, to have a 10px gap between Tooltip and its target: 202 | 203 | ```css 204 | .tooltip { 205 | top: 10px; 206 | } 207 | ``` 208 | 209 | You can also set it manually in options: 210 | 211 | ```js 212 | var tip = new Tooltip('foo', { spacing: 10 }); 213 | tip.show(); 214 | ``` 215 | 216 | The purpose of the `.tooltip:top` weirdness is to have all layout declarations in CSS and out of JavaScript. As the arrow size is declared 100% in CSS, so should be the gap needed for its proper display. 217 | 218 | ## #width 219 | 220 | Width of Tooltip element. Updated by `#updateSize()` method. 221 | 222 | ## #height 223 | 224 | Height of Tooltip element. Updated by `#updateSize()` method. 225 | 226 | ## #attachedTo 227 | 228 | Element the Tooltip is attached to, otherwise `null`. -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Tooltip Documentation 2 | 3 | - [API](API.md) 4 | - [Styling](Styling.md) -------------------------------------------------------------------------------- /docs/Styling.md: -------------------------------------------------------------------------------- 1 | # Styling 2 | 3 | Every tooltip's layout (what defines tooltip's margins, paddings, and overall dimensions) should be defined in `baseClass`. 4 | 5 | Arrow is styled with `:after` pseudo element. 6 | 7 | The gap between tooltip and its target position (so arrow won't overlap the target) is defined as a `top` property of `baseClass`, but can be overridden with `#options.spacing` property. 8 | 9 | The `baseClass` with minimum required styles will than look like this: 10 | 11 | ```css 12 | .tooltip { 13 | position: absolute; 14 | padding: .8em 1em; 15 | top: 15px; /* Defines the spacing between tooltip and target position */ 16 | max-width: 200px; 17 | color: #fff; 18 | background: #222; 19 | } 20 | ``` 21 | 22 | ## Place classes 23 | 24 | The current place is always attached to a tooltip as a class name. For example, when current tooltip place is `left-top`, the tooltip element will have a `left-top` class. 25 | 26 | ## Type class 27 | 28 | Type class should define only visual/color changes from the `baseClass`. This is what you'd use for states like `success`, `error`, ... 29 | 30 | ## In class 31 | 32 | The `inClass` doesn't have to be styled at all. It is used mainly as a "transitioning in" state selector for effect classes. 33 | 34 | ## Effect class 35 | 36 | The `effectClass` is attached to a tooltip at all times, and transitions are styled based on a presence of `inClass`. 37 | 38 | Example fade transition: 39 | 40 | ```css 41 | .tooltip.fade { opacity: 0; transition: opacity 200ms ease-out; } 42 | .tooltip.fade.in { opacity: 1; transition-duration: 100ms; } 43 | ``` 44 | 45 | When Tooltip is hiding the tip, it detects the presence of `transition-duration` and delays the tooltip DOM removal until the transition is over. 46 | 47 | Some transitions, like slide in, are dependent on toooltip position, in which case you'll leverage the place classes. 48 | 49 | Example slide transition class: 50 | 51 | ```css 52 | .tooltip.slide { 53 | opacity: 0; 54 | transition: -webkit-transform 200ms ease-out; 55 | transition: transform 200ms ease-out; 56 | transition-property: -webkit-transform, opacity; 57 | transition-property: transform, opacity; 58 | } 59 | .tooltip.slide.top, 60 | .tooltip.slide.top-left, 61 | .tooltip.slide.top-right { 62 | -webkit-transform: translateY(15px); 63 | transform: translateY(15px); 64 | } 65 | .tooltip.slide.bottom, 66 | .tooltip.slide.bottom-left, 67 | .tooltip.slide.bottom-right { 68 | -webkit-transform: translateY(-15px); 69 | transform: translateY(-15px); 70 | } 71 | .tooltip.slide.left, 72 | .tooltip.slide.left-top, 73 | .tooltip.slide.left-bottom { 74 | -webkit-transform: translateX(15px); 75 | transform: translateX(15px); 76 | } 77 | .tooltip.slide.right, 78 | .tooltip.slide.right-top, 79 | .tooltip.slide.right-bottom { 80 | -webkit-transform: translateX(-15px); 81 | transform: translateX(-15px); 82 | } 83 | .tooltip.slide.in { 84 | opacity: 1; 85 | -webkit-transform: none; 86 | transform: none; 87 | transition-duration: 100ms; 88 | } 89 | ``` 90 | 91 | ## Custom class types 92 | 93 | You can define custom class types by pushing their names into a `Tooltip.classTypes` array. By default this array looks like this: 94 | 95 | ```js 96 | Tooltip.classTypes = ['type', 'effect']; 97 | ``` 98 | 99 | It specifies the dynamic class types, that's why `base` & `in` are not in it, as those are core classes used for other purposes. 100 | 101 | To add a new dynamic class type `state`, you can do: 102 | 103 | ```js 104 | Tooltip.classTypes.push('state'); 105 | ``` 106 | 107 | You can than define this class in options like so: 108 | 109 | ```js 110 | var tip = new Tip('Foo bar', { 111 | stateClass: 'not-good' 112 | }) 113 | ``` 114 | 115 | And use in runtime: 116 | 117 | ```js 118 | tip.changeClassType('state', 'bit-better'); 119 | ``` 120 | 121 | Or you can extend `Tooltip` and define a `.state()` method: 122 | 123 | ```js 124 | Tooltip.prototype.state = function (name) { 125 | return this.changeClassType('state', name); 126 | }; 127 | ``` -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var style = require('computed-style'); 2 | var evt = require('event'); 3 | var extend = require('extend'); 4 | var classes = require('classes'); 5 | var indexOf = require('indexof'); 6 | var position = require('position'); 7 | var parseNumber = require('parse-number'); 8 | var transitionDuration = require('./lib/tduration'); 9 | 10 | var win = window; 11 | var doc = win.document; 12 | var body = doc.body; 13 | var verticalPlaces = ['top', 'bottom']; 14 | 15 | module.exports = Tooltip; 16 | 17 | /** 18 | * Tooltip construnctor. 19 | * 20 | * @param {String|Element} [content] 21 | * @param {Object} [options] 22 | * 23 | * @return {Tooltip} 24 | */ 25 | function Tooltip(content, options) { 26 | if (!(this instanceof Tooltip)) return new Tooltip(content, options); 27 | if (typeof content === 'object' && content.nodeType == null) { 28 | options = content; 29 | content = null; 30 | } 31 | this.hidden = true; 32 | this.options = extend(true, {}, Tooltip.defaults, options); 33 | this._createElement(); 34 | if (content) this.content(content); 35 | } 36 | 37 | /** 38 | * Creates a Tooltip element. 39 | * 40 | * @return {Void} 41 | */ 42 | Tooltip.prototype._createElement = function () { 43 | this.element = doc.createElement('div'); 44 | this.classes = classes(this.element); 45 | this.classes.add(this.options.baseClass); 46 | this.element.style.pointerEvents = this.options.interactive ? 'auto' : 'none'; 47 | var propName; 48 | for (var i = 0; i < Tooltip.classTypes.length; i++) { 49 | propName = Tooltip.classTypes[i] + 'Class'; 50 | if (this.options[propName]) this.classes.add(this.options[propName]); 51 | } 52 | }; 53 | 54 | /** 55 | * Changes Tooltip's type class type. 56 | * 57 | * @param {String} name 58 | * 59 | * @return {Tooltip} 60 | */ 61 | Tooltip.prototype.type = function (name) { 62 | return this.changeClassType('type', name); 63 | }; 64 | 65 | /** 66 | * Changes Tooltip's effect class type. 67 | * 68 | * @param {String} name 69 | * 70 | * @return {Tooltip} 71 | */ 72 | Tooltip.prototype.effect = function (name) { 73 | return this.changeClassType('effect', name); 74 | }; 75 | 76 | /** 77 | * Changes class type. 78 | * 79 | * @param {String} propName 80 | * @param {String} newClass 81 | * 82 | * @return {Tooltip} 83 | */ 84 | Tooltip.prototype.changeClassType = function (propName, newClass) { 85 | propName += 'Class'; 86 | if (this.options[propName]) this.classes.remove(this.options[propName]); 87 | this.options[propName] = newClass; 88 | if (newClass) this.classes.add(newClass); 89 | return this; 90 | }; 91 | 92 | /** 93 | * Updates Tooltip's dimensions. 94 | * 95 | * @return {Tooltip} 96 | */ 97 | Tooltip.prototype.updateSize = function () { 98 | if (this.hidden) { 99 | this.element.style.visibility = 'hidden'; 100 | body.appendChild(this.element); 101 | } 102 | this.width = this.element.offsetWidth; 103 | this.height = this.element.offsetHeight; 104 | if (this.spacing == null) this.spacing = this.options.spacing != null 105 | ? this.options.spacing 106 | : parseNumber(style(this.element).top); 107 | if (this.hidden) { 108 | body.removeChild(this.element); 109 | this.element.style.visibility = ''; 110 | } else { 111 | this.position(); 112 | } 113 | return this; 114 | }; 115 | 116 | /** 117 | * Change Tooltip content. 118 | * 119 | * When Tooltip is visible, its size is automatically 120 | * synced and Tooltip correctly repositioned. 121 | * 122 | * @param {String|Element} content 123 | * 124 | * @return {Tooltip} 125 | */ 126 | Tooltip.prototype.content = function (content) { 127 | if (typeof content === 'object') { 128 | this.element.innerHTML = ''; 129 | this.element.appendChild(content); 130 | } else { 131 | this.element.innerHTML = content; 132 | } 133 | this.updateSize(); 134 | return this; 135 | }; 136 | 137 | /** 138 | * Pick new place Tooltip should be displayed at. 139 | * 140 | * When the Tooltip is visible, it is automatically positioned there. 141 | * 142 | * @param {String} place 143 | * 144 | * @return {Tooltip} 145 | */ 146 | Tooltip.prototype.place = function (place) { 147 | this.options.place = place; 148 | if (!this.hidden) this.position(); 149 | return this; 150 | }; 151 | 152 | /** 153 | * Attach Tooltip to an element. 154 | * 155 | * @param {Element} element 156 | * 157 | * @return {Tooltip} 158 | */ 159 | Tooltip.prototype.attach = function (element) { 160 | this.attachedTo = element; 161 | if (!this.hidden) this.position(); 162 | return this; 163 | }; 164 | 165 | /** 166 | * Detach Tooltip from element. 167 | * 168 | * @return {Tooltip} 169 | */ 170 | Tooltip.prototype.detach = function () { 171 | this.hide(); 172 | delete this.attachedTo; 173 | return this; 174 | }; 175 | 176 | /** 177 | * Pick the most reasonable place for target position. 178 | * 179 | * @param {Object} target 180 | * 181 | * @return {Tooltip} 182 | */ 183 | Tooltip.prototype._pickPlace = function (target) { 184 | if (!this.options.auto) return this.options.place; 185 | var winPos = position(win); 186 | var place = this.options.place.split('-'); 187 | var spacing = this.spacing; 188 | 189 | if (~indexOf(verticalPlaces, place[0])) { 190 | if (target.top - this.height - spacing <= winPos.top) place[0] = 'bottom'; 191 | else if (target.bottom + this.height + spacing >= winPos.bottom) place[0] = 'top'; 192 | switch (place[1]) { 193 | case 'left': 194 | if (target.right - this.width <= winPos.left) place[1] = 'right'; 195 | break; 196 | case 'right': 197 | if (target.left + this.width >= winPos.right) place[1] = 'left'; 198 | break; 199 | default: 200 | if (target.left + target.width / 2 + this.width / 2 >= winPos.right) place[1] = 'left'; 201 | else if (target.right - target.width / 2 - this.width / 2 <= winPos.left) place[1] = 'right'; 202 | } 203 | } else { 204 | if (target.left - this.width - spacing <= winPos.left) place[0] = 'right'; 205 | else if (target.right + this.width + spacing >= winPos.right) place[0] = 'left'; 206 | switch (place[1]) { 207 | case 'top': 208 | if (target.bottom - this.height <= winPos.top) place[1] = 'bottom'; 209 | break; 210 | case 'bottom': 211 | if (target.top + this.height >= winPos.bottom) place[1] = 'top'; 212 | break; 213 | default: 214 | if (target.top + target.height / 2 + this.height / 2 >= winPos.bottom) place[1] = 'top'; 215 | else if (target.bottom - target.height / 2 - this.height / 2 <= winPos.top) place[1] = 'bottom'; 216 | } 217 | } 218 | 219 | return place.join('-'); 220 | }; 221 | 222 | /** 223 | * Position the Tooltip to an element or a specific coordinates. 224 | * 225 | * @param {Integer|Element} x 226 | * @param {Integer} y 227 | * 228 | * @return {Tooltip} 229 | */ 230 | Tooltip.prototype.position = function (x, y) { 231 | x = this.attachedTo || x; 232 | if (x == null && this._p) { 233 | x = this._p[0]; 234 | y = this._p[1]; 235 | } else { 236 | this._p = arguments; 237 | } 238 | var target = typeof x === 'number' ? { 239 | left: 0|x, 240 | right: 0|x, 241 | top: 0|y, 242 | bottom: 0|y, 243 | width: 0, 244 | height: 0 245 | } : position(x); 246 | var spacing = this.spacing; 247 | var newPlace = this._pickPlace(target); 248 | 249 | // Add/Change place class when necessary 250 | if (newPlace !== this.curPlace) { 251 | if (this.curPlace) this.classes.remove(this.curPlace); 252 | this.classes.add(newPlace); 253 | this.curPlace = newPlace; 254 | } 255 | 256 | // Position the tip 257 | var top, left; 258 | switch (this.curPlace) { 259 | case 'top': 260 | top = target.top - this.height - spacing; 261 | left = target.left + target.width / 2 - this.width / 2; 262 | break; 263 | case 'top-left': 264 | top = target.top - this.height - spacing; 265 | left = target.right - this.width; 266 | break; 267 | case 'top-right': 268 | top = target.top - this.height - spacing; 269 | left = target.left; 270 | break; 271 | 272 | case 'bottom': 273 | top = target.bottom + spacing; 274 | left = target.left + target.width / 2 - this.width / 2; 275 | break; 276 | case 'bottom-left': 277 | top = target.bottom + spacing; 278 | left = target.right - this.width; 279 | break; 280 | case 'bottom-right': 281 | top = target.bottom + spacing; 282 | left = target.left; 283 | break; 284 | 285 | case 'left': 286 | top = target.top + target.height / 2 - this.height / 2; 287 | left = target.left - this.width - spacing; 288 | break; 289 | case 'left-top': 290 | top = target.bottom - this.height; 291 | left = target.left - this.width - spacing; 292 | break; 293 | case 'left-bottom': 294 | top = target.top; 295 | left = target.left - this.width - spacing; 296 | break; 297 | 298 | case 'right': 299 | top = target.top + target.height / 2 - this.height / 2; 300 | left = target.right + spacing; 301 | break; 302 | case 'right-top': 303 | top = target.bottom - this.height; 304 | left = target.right + spacing; 305 | break; 306 | case 'right-bottom': 307 | top = target.top; 308 | left = target.right + spacing; 309 | break; 310 | } 311 | 312 | // Set tip position & class 313 | this.element.style.top = Math.round(top) + 'px'; 314 | this.element.style.left = Math.round(left) + 'px'; 315 | 316 | return this; 317 | }; 318 | 319 | /** 320 | * Show the Tooltip. 321 | * 322 | * @return {Tooltip} 323 | */ 324 | Tooltip.prototype.show = function () { 325 | // Clear potential ongoing animation 326 | clearTimeout(this.aIndex); 327 | 328 | // Position the element when needed 329 | if (this.attachedTo) this.position(this.attachedTo); 330 | 331 | // Stop here if tip is already visible 332 | if (this.hidden) { 333 | this.hidden = false; 334 | body.appendChild(this.element); 335 | } 336 | 337 | // Make Tooltip aware of window resize 338 | if (this.attachedTo) this._aware(); 339 | 340 | // Trigger layout and kick in the transition 341 | if (this.options.inClass) { 342 | if (this.options.effectClass) void this.element.clientHeight; 343 | this.classes.add(this.options.inClass); 344 | } 345 | 346 | return this; 347 | }; 348 | 349 | /** 350 | * Hide the Tooltip. 351 | * 352 | * @return {Tooltip} 353 | */ 354 | Tooltip.prototype.hide = function () { 355 | if (this.hidden) return; 356 | 357 | var self = this; 358 | var duration = 0; 359 | 360 | // Remove .in class and calculate transition duration if any 361 | if (this.options.inClass) { 362 | this.classes.remove(this.options.inClass); 363 | if (this.options.effectClass) duration = transitionDuration(this.element); 364 | } 365 | 366 | // Remove tip from window resize awareness 367 | if (this.attachedTo) this._unaware(); 368 | 369 | // Remove the tip from the DOM when transition is done 370 | clearTimeout(this.aIndex); 371 | this.aIndex = setTimeout(function () { 372 | self.aIndex = 0; 373 | body.removeChild(self.element); 374 | self.hidden = true; 375 | }, duration); 376 | 377 | return this; 378 | }; 379 | 380 | /** 381 | * Hide Tooltip when shown, or show when hidden. 382 | * 383 | * @return {Tooltip} 384 | */ 385 | Tooltip.prototype.toggle = function () { 386 | return this[this.hidden ? 'show' : 'hide'](); 387 | }; 388 | 389 | /** 390 | * Make the Tooltip window resize aware. 391 | * 392 | * @return {Void} 393 | */ 394 | Tooltip.prototype._aware = function () { 395 | var index = indexOf(Tooltip.winAware, this); 396 | if (!~index) Tooltip.winAware.push(this); 397 | }; 398 | 399 | /** 400 | * Remove the window resize awareness. 401 | * 402 | * @return {Void} 403 | */ 404 | Tooltip.prototype._unaware = function () { 405 | var index = indexOf(Tooltip.winAware, this); 406 | if (~index) Tooltip.winAware.splice(index, 1); 407 | }; 408 | 409 | /** 410 | * Destroy Tooltip instance. 411 | */ 412 | Tooltip.prototype.destroy = function () { 413 | clearTimeout(this.aIndex); 414 | this._unaware(); 415 | if (!this.hidden) body.removeChild(this.element); 416 | this.element = this.options = null; 417 | }; 418 | 419 | /** 420 | * Handles repositioning of Tooltips on window resize. 421 | * 422 | * @return {Void} 423 | */ 424 | Tooltip.reposition = (function () { 425 | var rAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame || function (fn) { 426 | return setTimeout(fn, 17); 427 | }; 428 | var rIndex; 429 | 430 | function requestReposition() { 431 | if (rIndex || !Tooltip.winAware.length) return; 432 | rIndex = rAF(reposition, 17); 433 | } 434 | 435 | function reposition() { 436 | rIndex = 0; 437 | var tip; 438 | for (var i = 0, l = Tooltip.winAware.length; i < l; i++) { 439 | tip = Tooltip.winAware[i]; 440 | tip.position(); 441 | } 442 | } 443 | 444 | return requestReposition; 445 | }()); 446 | Tooltip.winAware = []; 447 | 448 | // Bind winAware repositioning to window resize event 449 | evt.bind(window, 'resize', Tooltip.reposition); 450 | evt.bind(window, 'scroll', Tooltip.reposition); 451 | 452 | /** 453 | * Array with dynamic class types. 454 | * 455 | * @type {Array} 456 | */ 457 | Tooltip.classTypes = ['type', 'effect']; 458 | 459 | /** 460 | * Default options for Tooltip constructor. 461 | * 462 | * @type {Object} 463 | */ 464 | Tooltip.defaults = { 465 | baseClass: 'tooltip', // Base Tooltip class name. 466 | typeClass: null, // Type Tooltip class name. 467 | effectClass: null, // Effect Tooltip class name. 468 | inClass: 'in', // Class used to transition stuff in. 469 | place: 'top', // Default place. 470 | spacing: null, // Gap between target and Tooltip. 471 | interactive: false, // Whether Tooltip should be interactive, or click through. 472 | auto: 0 // Whether to automatically adjust place to fit into window. 473 | }; -------------------------------------------------------------------------------- /lib/tduration.js: -------------------------------------------------------------------------------- 1 | var style = require('computed-style'); 2 | 3 | /** 4 | * Returns transition duration of an element in ms. 5 | * 6 | * @param {Element} element 7 | * 8 | * @return {Int} 9 | */ 10 | module.exports = function transitionDuration(element) { 11 | var computed = style(element); 12 | var duration = String(computed.transitionDuration || computed.webkitTransitionDuration); 13 | var match = duration.match(/([0-9.]+)([ms]{1,2})/); 14 | if (match) { 15 | duration = Number(match[1]); 16 | if (match[2] === 's') duration *= 1000; 17 | } 18 | return duration | 0; 19 | }; -------------------------------------------------------------------------------- /meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tooltip", 3 | "description": "Tooltip component.", 4 | "version": "0.1.0", 5 | "date": "2013-12-17 14:07:20 +00:00", 6 | "bugs": "https://github.com/darsain/tooltip/issues", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/darsain/tooltip.git", 10 | "homepage": "https://github.com/darsain/tooltip" 11 | }, 12 | "licenses": [ 13 | { 14 | "type": "MIT", 15 | "url": "http://opensource.org/licenses/MIT" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tooltip", 3 | "version": "0.0.0", 4 | "devDependencies": { 5 | "grunt-contrib-compress": "~0.5.3", 6 | "grunt-contrib-uglify": "~0.2.7", 7 | "grunt-contrib-jshint": "~0.7.2", 8 | "grunt-contrib-concat": "~0.3.0", 9 | "grunt-contrib-clean": "~0.5.0", 10 | "grunt-contrib-watch": "~0.5.3", 11 | "grunt-contrib-copy": "~0.4.1", 12 | "grunt-component-build": "~0.4.2", 13 | "grunt-tagrelease": "~0.3.0", 14 | "grunt-bumpup": "~0.4.2", 15 | "grunt": "~0.4.2" 16 | } 17 | } -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tooltip test 6 | 7 | 8 | 9 | 10 | 11 |
12 |
target
13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 | 21 |
22 |
23 | 24 | 25 | 26 |
27 |
28 | 29 | 30 | 31 |
32 |
33 | 34 | 35 | 36 |
37 |
38 |

39 | 40 | 41 | 42 |

43 |

44 | 45 | 46 | 47 | 48 |

49 |

50 | 51 | 52 | 53 | 54 |

55 |
56 | 57 |
58 | 59 |
60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /test/test.css: -------------------------------------------------------------------------------- 1 | html, body { margin: 0; padding: 1px 0; color: #444; font-size: 1em; font-family: sans-serif; background: #ddd; } 2 | 3 | .target { 4 | position: relative; 5 | width: 100px; 6 | /*height: 50px;*/ 7 | line-height: 50px; 8 | margin: 10px 5px; 9 | text-align: center; 10 | text-shadow: 1px 1px rgba(255,255,255,.5); 11 | border: 1px dashed rgba(255,255,255,.8); 12 | border-radius: 4px; 13 | background: inherit; 14 | } 15 | .target:after { 16 | content: ''; 17 | position: absolute; 18 | top: -2px; 19 | left: -2px; 20 | width: 100%; 21 | height: 100%; 22 | border: 1px dashed #666; 23 | border-radius: 4px; 24 | } 25 | .target.movable { margin: 0 auto; cursor: move; } 26 | 27 | .wrapper { 28 | width: 900px; 29 | margin: 120px auto; 30 | text-align: center; 31 | } 32 | 33 | button { padding: 5px; } 34 | 35 | .controls { position: relative; } 36 | .controls .places { position: relative; width: 500px; height: 30px; margin: -20px auto 0; } 37 | .controls .places button { width: 90px; } 38 | .controls .places .top, .controls .places .left, .controls .places .right, .controls .places .bottom { position: absolute; } 39 | .controls .places .top { top: 0; width: 100%; } 40 | .controls .places .bottom { top: 200px; width: 100%; } 41 | .controls .places .left { top: 60px; left: 0; } 42 | .controls .places .right { top: 60px; right: 0; text-align: right; } 43 | .controls .places .vertical button { display: block; } -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var Tooltip = require('tooltip'); 2 | var $ = require('jquery'); 3 | 4 | // Movable test 5 | (function () { 6 | var target = document.querySelector('.target.movable'); 7 | var tip = window.tip = new Tooltip('Move the element toward window endges to test the automatic positioning.', { auto: 1 }); 8 | var dragger = new DragAndReset(target); 9 | 10 | tip.attach(target).show(); 11 | 12 | dragger.onMove = function reposition() { 13 | tip.position(); 14 | }; 15 | 16 | // Button actions 17 | $(document).on('mousedown', '[data-action]', function () { 18 | var action = $(this).data('action'); 19 | var arg = $(this).data('arg'); 20 | tip[action](arg); 21 | }); 22 | }()); 23 | 24 | // Focusable test 25 | (function () { 26 | var $target = $('.focusable').eq(0); 27 | var tip = new Tooltip('', { place: 'right', typeClass: '', effectClass: 'slide' }).attach($target[0]); 28 | var toEnter = 'foo'; 29 | var value, correct; 30 | 31 | function check() { 32 | value = $target.val(); 33 | correct = value === toEnter; 34 | if (!value.length) { 35 | tip.content('Write "' + toEnter + '"'); 36 | tip.type(''); 37 | return; 38 | } 39 | tip.type(correct ? 'success' : 'error').content(correct ? 'Correct!' : 'Wrong!'); 40 | } 41 | 42 | $target.on('focus blur', function (event) { 43 | if (event.type === 'blur' && !correct && value.length > 0) { 44 | return; 45 | } 46 | tip[event.type === 'focus' ? 'show' : 'hide'](); 47 | }); 48 | 49 | $target.on('keyup', check); 50 | 51 | check(); 52 | }()); 53 | 54 | // Helpers 55 | var rAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame || function (callback) { 56 | return setTimeout(callback, 17); 57 | }; 58 | var getProp = window.getComputedStyle ? function getProp(element, name) { 59 | return window.getComputedStyle(element, null)[name]; 60 | } : function getProp(element, name) { 61 | return element.currentStyle[name]; 62 | }; 63 | function parsePx(value) { 64 | return 0 | Math.round(String(value).replace(/[^\-0-9.]/g, '')); 65 | } 66 | 67 | /** 68 | * Dragging class 69 | * 70 | * @param {Element} element 71 | */ 72 | function DragAndReset(element) { 73 | if (!(this instanceof DragAndReset)) { 74 | return new DragAndReset(element); 75 | } 76 | 77 | var self = this; 78 | var $document = $(document); 79 | var frameID = 0; 80 | self.element = element; 81 | self.initialized = 0; 82 | self.path = { 83 | left: 0, 84 | top: 0 85 | }; 86 | 87 | function move(event) { 88 | self.path.left = event.pageX - self.origin.left; 89 | self.path.top = event.pageY - self.origin.top; 90 | if (!self.initialized && (Math.abs(self.path.left) > 10 || Math.abs(self.path.top) > 10)) { 91 | self.initialized = 1; 92 | } 93 | if (self.initialized) { 94 | requestReposition(); 95 | } 96 | return false; 97 | } 98 | 99 | function requestReposition() { 100 | if (!frameID) { 101 | frameID = rAF(reposition); 102 | } 103 | } 104 | 105 | function reposition() { 106 | frameID = 0; 107 | element.style.left = (self.originPos.left + self.path.left) + 'px'; 108 | element.style.top = (self.originPos.top + self.path.top) + 'px'; 109 | if (self.onMove) { 110 | self.onMove(); 111 | } 112 | } 113 | 114 | function init(event) { 115 | self.origin = { 116 | left: event.pageX, 117 | top: event.pageY 118 | }; 119 | self.originPos = { 120 | left: parsePx(getProp(element, 'left')), 121 | top: parsePx(getProp(element, 'top')) 122 | }; 123 | $document.on('mousemove', move); 124 | $document.on('mouseup', self.end); 125 | return false; 126 | } 127 | 128 | self.end = function () { 129 | self.initialized = 0; 130 | self.path.top = self.path.left = 0; 131 | requestReposition(); 132 | $document.off('mousemove', move); 133 | $document.off('mouseup', self.end); 134 | }; 135 | 136 | (function () { 137 | $(element).on('mousedown', init); 138 | }()); 139 | } -------------------------------------------------------------------------------- /tooltip.css: -------------------------------------------------------------------------------- 1 | .tooltip { 2 | position: absolute; 3 | padding: .8em 1em; 4 | top: 10px; /* Defines the spacing between tooltip and target position */ 5 | max-width: 200px; 6 | color: #fff; 7 | background: #3a3c47; 8 | border-radius: 2px; 9 | text-shadow: -1px -1px 0 rgba(0,0,0,.2); 10 | } 11 | 12 | /* Arrow styles */ 13 | .tooltip:after { 14 | content: ''; 15 | position: absolute; 16 | width: 10px; 17 | height: 10px; 18 | margin: -5px; 19 | background: inherit; 20 | -webkit-transform: rotate(45deg); 21 | -ms-transform: rotate(45deg); 22 | transform: rotate(45deg); 23 | } 24 | 25 | .tooltip.top:after, .tooltip.top-left:after, .tooltip.top-right:after { bottom: 0; } 26 | .tooltip.bottom:after, .tooltip.bottom-left:after, .tooltip.bottom-right:after { top: 0; } 27 | .tooltip.top:after, .tooltip.bottom:after { left: 50%; } 28 | .tooltip.top-left:after, .tooltip.bottom-left:after { right: 15px; } 29 | .tooltip.top-right:after, .tooltip.bottom-right:after { left: 15px; } 30 | 31 | .tooltip.left:after, .tooltip.left-top:after, .tooltip.left-bottom:after { right: 0; } 32 | .tooltip.right:after, .tooltip.right-top:after, .tooltip.right-bottom:after { left: 0; } 33 | .tooltip.left:after, .tooltip.right:after { top: 50%; } 34 | .tooltip.left-top:after, .tooltip.right-top:after { bottom: 15px; } 35 | .tooltip.left-bottom:after, .tooltip.right-bottom:after { top: 15px; } 36 | -------------------------------------------------------------------------------- /transitions.css: -------------------------------------------------------------------------------- 1 | /* Fade */ 2 | .tooltip.fade { opacity: 0; transition: opacity 200ms ease-out; } 3 | .tooltip.fade.in { opacity: 1; transition-duration: 100ms; } 4 | 5 | /* Slide */ 6 | .tooltip.slide { 7 | opacity: 0; 8 | transition: -webkit-transform 200ms ease-out; 9 | transition: transform 200ms ease-out; 10 | transition-property: -webkit-transform, opacity; 11 | transition-property: transform, opacity; 12 | } 13 | .tooltip.slide.top, 14 | .tooltip.slide.top-left, 15 | .tooltip.slide.top-right { 16 | -webkit-transform: translateY(15px); 17 | transform: translateY(15px); 18 | } 19 | .tooltip.slide.bottom, 20 | .tooltip.slide.bottom-left, 21 | .tooltip.slide.bottom-right { 22 | -webkit-transform: translateY(-15px); 23 | transform: translateY(-15px); 24 | } 25 | .tooltip.slide.left, 26 | .tooltip.slide.left-top, 27 | .tooltip.slide.left-bottom { 28 | -webkit-transform: translateX(15px); 29 | transform: translateX(15px); 30 | } 31 | .tooltip.slide.right, 32 | .tooltip.slide.right-top, 33 | .tooltip.slide.right-bottom { 34 | -webkit-transform: translateX(-15px); 35 | transform: translateX(-15px); 36 | } 37 | .tooltip.slide.in { 38 | opacity: 1; 39 | -webkit-transform: none; 40 | transform: none; 41 | transition-duration: 100ms; 42 | } 43 | 44 | /* Grow */ 45 | .tooltip.grow { 46 | -webkit-transform: scale(0); 47 | transform: scale(0); 48 | transition: -webkit-transform 200ms ease-out; 49 | transition: transform 200ms ease-out; 50 | } 51 | .tooltip.grow.top { 52 | -webkit-transform: translateY(60%) scale(0); 53 | transform: translateY(60%) scale(0); 54 | } 55 | .tooltip.grow.top-left { 56 | -webkit-transform: translateY(60%) translateX(40%) scale(0); 57 | transform: translateY(60%) translateX(40%) scale(0); 58 | } 59 | .tooltip.grow.top-right { 60 | -webkit-transform: translateY(60%) translateX(-40%) scale(0); 61 | transform: translateY(60%) translateX(-40%) scale(0); 62 | } 63 | .tooltip.grow.bottom { 64 | -webkit-transform: translateY(-60%) scale(0); 65 | transform: translateY(-60%) scale(0); 66 | } 67 | .tooltip.grow.bottom-left { 68 | -webkit-transform: translateY(-60%) translateX(40%) scale(0); 69 | transform: translateY(-60%) translateX(40%) scale(0); 70 | } 71 | .tooltip.grow.bottom-right { 72 | -webkit-transform: translateY(-60%) translateX(-40%) scale(0); 73 | transform: translateY(-60%) translateX(-40%) scale(0); 74 | } 75 | .tooltip.grow.left { 76 | -webkit-transform: translateX(53%) scale(0); 77 | transform: translateX(53%) scale(0); 78 | } 79 | .tooltip.grow.left-top { 80 | -webkit-transform: translateX(53%) translateY(40%) scale(0); 81 | transform: translateX(53%) translateY(40%) scale(0); 82 | } 83 | .tooltip.grow.left-bottom { 84 | -webkit-transform: translateX(53%) translateY(-40%) scale(0); 85 | transform: translateX(53%) translateY(-40%) scale(0); 86 | } 87 | .tooltip.grow.right { 88 | -webkit-transform: translateX(-53%) scale(0); 89 | transform: translateX(-53%) scale(0); 90 | } 91 | .tooltip.grow.right-top { 92 | -webkit-transform: translateX(-53%) translateY(40%) scale(0); 93 | transform: translateX(-53%) translateY(40%) scale(0); 94 | } 95 | .tooltip.grow.right-bottom { 96 | -webkit-transform: translateX(-53%) translateY(-40%) scale(0); 97 | transform: translateX(-53%) translateY(-40%) scale(0); 98 | } 99 | .tooltip.grow.in { 100 | -webkit-transform: none; 101 | transform: none; 102 | transition-duration: 100ms; 103 | } 104 | -------------------------------------------------------------------------------- /types.css: -------------------------------------------------------------------------------- 1 | /* Types */ 2 | .tooltip.light { color: #3a3c47; background: #fff; text-shadow: none; } 3 | .tooltip.success { background: #8dc572; } 4 | .tooltip.warning { background: #ddc12e; } 5 | .tooltip.error { background: #be6464; } 6 | --------------------------------------------------------------------------------