├── LICENSE ├── ticker.jquery.json ├── jquery.ticker.min.js ├── README.md └── jquery.ticker.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Benjamin Harris 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /ticker.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ticker", 3 | "title": "jQuery Ticker", 4 | "description": "A lightweight jQuery plugin for animating a simple news ticker.", 5 | "keywords": [ 6 | "ticker", 7 | "news", 8 | "marquee", 9 | "news-ticker" 10 | ], 11 | "version": "v1.2.1", 12 | "author": { 13 | "name": "Benjamin Harris", 14 | "url": "https://github.com/BenjaminRH" 15 | }, 16 | "maintainers": [ 17 | { 18 | "name": "Benjamin Harris", 19 | "email": "benjamin3harris@gmail.com", 20 | "url": "https://github.com/BenjaminRH" 21 | } 22 | ], 23 | "licenses": [ 24 | { 25 | "type": "MIT", 26 | "url": "https://raw.githubusercontent.com/BenjaminRH/jquery-ticker/master/LICENSE" 27 | } 28 | ], 29 | "bugs": "https://github.com/BenjaminRH/jquery-ticker/issues", 30 | "homepage": "https://benjaminrh.github.io/jquery-ticker", 31 | "docs": "https://benjaminrh.github.io/jquery-ticker#usage", 32 | "demo": "https://benjaminrh.github.io/jquery-ticker/#demo", 33 | "dependencies": { 34 | "jquery": ">=1.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /jquery.ticker.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery Ticker Plugin v1.2.1 | https://github.com/BenjaminRH/jquery-ticker | Copyright 2014 Benjamin Harris | Released under the MIT license */ 2 | (function(l){function D(c){for(var a=c.length-1;0]*>/img,function(c,b){return-1!==a.indexOf(b.toLowerCase())?c:""})}l.fn.ticker=function(c){var a=l.extend({},l.fn.ticker.defaults,c);return this.each(function(){function c(){u=!0;y?(y=!1,b()):z=setTimeout(function(){a.pauseOnHover&&e.hasClass("hover")?(clearTimeout(h),c()):b()},a.itemSpeed)}function b(){u?(u=!1,A()):d>k[f].length?B():a.finishOnHover&&a.pauseOnHover&&e.hasClass("hover")&&d<=k[f].length?(p.html(v()),d+=1,b()):h=setTimeout(function(){a.pauseOnHover&&e.hasClass("hover")?(clearTimeout(h),b()):(A(),B())},a.cursorSpeed)}function B(){d>k[f].length&&(f+=1,d=0,f==k.length&&(f=0),clearTimeout(h),clearTimeout(z),c())}function A(){0===d&&a.fade?(clearTimeout(h),p.fadeOut(a.fadeOutSpeed,function(){p.html(v());p.fadeIn(a.fadeInSpeed,function(){d+=1;b()})})):(p.html(v()),d+=1,clearTimeout(h),b())}function v(){var c,b,q,m;switch(d%2){case 1:c=a.cursorOne;break;case 0:c=a.cursorTwo}d>=k[f].length&&(c="");var n="",e=[];for(b=0;b";return n+c}var e=l(this),p,E=e.find("li"),k=[],r={},z,h,f=0,d=0,y=!0,u=!0,F="a b strong span i em u".split(" ");if(a.finishOnHover||a.pauseOnHover)e.removeClass("hover"),e.hover(function(){l(this).toggleClass("hover")});var t,C;E.each(function(a,c){var b=t=x(l(this).html(),F),d;d=[];for(var e=/<\/?([a-z][a-z0-9]*)\b[^>]*>/im,f=/\/\s{0,}>$/m,h=[],g;null!==(g=e.exec(b));)if(0===d.length||-1!==d.indexOf(g[1]))g={tag:g[0],name:g[1],selfClosing:f.test(g[0]),start:g.index,end:g.index+g[0].length-1},h.push(g),b=b.slice(0,g.start)+b.slice(g.end+1),e.lastIndex=0;C=h;t=x(t);k.push(t);r[k.length-1]=C});a.random&&D(k);e.find("ul").after("
").remove();p=e.find("div");c()})};l.fn.ticker.defaults={random:!1,itemSpeed:3E3,cursorSpeed:50,pauseOnHover:!0,finishOnHover:!0,cursorOne:"_",cursorTwo:"-",fade:!0,fadeInSpeed:600,fadeOutSpeed:300}})(jQuery); 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jquery-ticker 2 | ============= 3 | 4 | A lightweight jQuery plugin for animating a simple news ticker 5 | 6 | 7 | ## Installation 8 | 9 | Include script after the jQuery library: 10 | 11 | ```html 12 | 13 | ``` 14 | 15 | 16 | ## Usage 17 | 18 | Add an HTML tag for the ticker container (like a `div` or a `span`) with one unordered list containing the ticker items inside. Other content may be inside the ticker container as well. 19 | 20 | Ticker headlines (the `li` tags) may contain the following basic HTML tags: ``, ``, ``, ``, ``, ``, and ``. 21 | 22 | ```html 23 |
24 | News: 25 |
    26 |
  • Ticker item #1
  • 27 |
  • Ticker item #2
  • 28 |
  • Another ticker item
  • 29 | ... 30 |
31 |
32 | ``` 33 | 34 | And initiate it 35 | 36 | ```javascript 37 | $('.ticker').ticker(); 38 | ``` 39 | 40 | You can even add some styling 41 | 42 | ```css 43 | .ticker { 44 | width: 500px; 45 | margin: 10px auto; 46 | } 47 | /* The HTML list gets replaced with a single div, 48 | which contains the active ticker item, so you 49 | can easily style that as well */ 50 | .ticker div { 51 | display: inline-block; 52 | word-wrap: break-word; 53 | } 54 | ``` 55 | 56 | 57 | ## Ticker Options 58 | 59 | Ticker attributes can be set globally by setting properties of the `$.ticker.defaults` object or individually for each call to `ticker()` by passing a plain object to the options argument. Per-call options override the default options. 60 | 61 | ```javascript 62 | $.fn.ticker.defaults = { 63 | random: false, // Whether to display ticker items in a random order 64 | itemSpeed: 3000, // The pause on each ticker item before being replaced 65 | cursorSpeed: 50, // Speed at which the characters are typed 66 | pauseOnHover: true, // Whether to pause when the mouse hovers over the ticker 67 | finishOnHover: true, // Whether or not to complete the ticker item instantly when moused over 68 | cursorOne: '_', // The symbol for the first part of the cursor 69 | cursorTwo: '-', // The symbol for the second part of the cursor 70 | fade: true, // Whether to fade between ticker items or not 71 | fadeInSpeed: 600, // Speed of the fade-in animation 72 | fadeOutSpeed: 300 // Speed of the fade-out animation 73 | }; 74 | ``` 75 | 76 | 77 | ## Authors 78 | 79 | [Benjamin Harris](https://github.com/BenjaminRH) 80 | -------------------------------------------------------------------------------- /jquery.ticker.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Ticker Plugin v1.2.1 3 | * https://github.com/BenjaminRH/jquery-ticker 4 | * 5 | * Copyright 2014 Benjamin Harris 6 | * Released under the MIT license 7 | */ 8 | (function($) { 9 | 10 | // The ticker plugin 11 | $.fn.ticker = function(options) { 12 | // Extend our defaults with user-specified options 13 | var opts = $.extend({}, $.fn.ticker.defaults, options); 14 | 15 | // Handle each of the given containers 16 | return this.each(function () { 17 | // Setup the ticker elements 18 | var tickerContainer = $(this); // Outer-most ticker container 19 | var headlineContainer; // Inner headline container 20 | var headlineElements = tickerContainer.find('li'); // Original headline elements 21 | var headlines = []; // List of all the headlines 22 | var headlineTagMap = {}; // Maps the indexes of the HTML tags in the headlines to the headline index 23 | var outerTimeoutId; // Stores the outer ticker timeout id for pauses 24 | var innerTimeoutId; // Stores the inner ticker timeout id for pauses 25 | var currentHeadline = 0; // The index of the current headline in the list of headlines 26 | var currentHeadlinePosition = 0; // The index of the current character in the current headline 27 | var firstOuterTick = true; // Whether this is the first time doing the outer tick 28 | var firstInnerTick = true; // Whether this is the first time doing the inner tick in this rendition of the outer one 29 | 30 | var allowedTags = ['a', 'b', 'strong', 'span', 'i', 'em', 'u']; 31 | 32 | if (opts.finishOnHover || opts.pauseOnHover) { 33 | // Setup monitoring hover state 34 | tickerContainer.removeClass('hover'); 35 | tickerContainer.hover(function() { 36 | $(this).toggleClass('hover'); 37 | }); 38 | } 39 | 40 | // Save all the headline text 41 | var h, l; 42 | headlineElements.each(function (index, element) { 43 | h = stripTags($(this).html(), allowedTags); // Strip all but the allowed tags 44 | l = locateTags(h); // Get the locations of the allowed tags 45 | h = stripTags(h); // Remove all of the HTML tags from the headline 46 | headlines.push(h); // Add the headline to the headlines list 47 | headlineTagMap[headlines.length - 1] = l; // Associate the tag map with the headline 48 | }); 49 | 50 | // Randomize? 51 | if (opts.random) shuffleArray(headlines); 52 | 53 | // Now delete all the elements and add the headline container 54 | tickerContainer.find('ul').after('
').remove(); 55 | headlineContainer = tickerContainer.find('div'); 56 | 57 | // Function to actually do the outer ticker, and handle pausing 58 | function outerTick() { 59 | firstInnerTick = true; 60 | 61 | if (firstOuterTick) { 62 | firstOuterTick = false; 63 | innerTick(); 64 | return; 65 | } 66 | 67 | outerTimeoutId = setTimeout(function () { 68 | if (opts.pauseOnHover && tickerContainer.hasClass('hover')) { 69 | // User is hovering over the ticker and pause on hover is enabled 70 | clearTimeout(innerTimeoutId); 71 | outerTick(); 72 | return; 73 | } 74 | 75 | innerTick(); 76 | }, opts.itemSpeed); 77 | } 78 | 79 | // Function to handle the ticking for individual headlines 80 | function innerTick() { 81 | if (firstInnerTick) { 82 | firstInnerTick = false; 83 | tick(); 84 | return; 85 | } 86 | 87 | if (currentHeadlinePosition > headlines[currentHeadline].length) { 88 | advance(); 89 | return; 90 | } 91 | 92 | if (opts.finishOnHover && opts.pauseOnHover && tickerContainer.hasClass('hover') && currentHeadlinePosition <= headlines[currentHeadline].length) { 93 | // Let's quickly complete the headline 94 | // This is outside the timeout because we want to do this instantly without the pause 95 | 96 | // Update the text 97 | headlineContainer.html(getCurrentTick()); 98 | // Advance our position 99 | currentHeadlinePosition += 1; 100 | 101 | innerTick(); 102 | return; 103 | } 104 | else { 105 | // Handle as normal 106 | innerTimeoutId = setTimeout(function () { 107 | if (opts.pauseOnHover && tickerContainer.hasClass('hover')) { 108 | // User is hovering over the ticker and pause on hover is enabled 109 | clearTimeout(innerTimeoutId); 110 | innerTick(); 111 | return; 112 | } 113 | 114 | tick(); 115 | advance(); 116 | }, opts.cursorSpeed); 117 | } 118 | } 119 | 120 | function advance() { 121 | // Advance headline and reset character position, if it's at the end of the current headline 122 | if (currentHeadlinePosition > headlines[currentHeadline].length) { // > here and not == because the ticker cursor takes an extra loop 123 | currentHeadline += 1; 124 | currentHeadlinePosition = 0; 125 | 126 | // Reset the headline and character positions if we've cycled through all the headlines 127 | if (currentHeadline == headlines.length) currentHeadline = 0; 128 | 129 | // STOP! We've advanced a headline. Now we just need to pause. 130 | clearTimeout(innerTimeoutId); 131 | clearTimeout(outerTimeoutId); 132 | outerTick(); 133 | } 134 | } 135 | 136 | // Do the individual ticks 137 | function tick() { 138 | // Now let's update the ticker with the current tick string 139 | if (currentHeadlinePosition === 0 && opts.fade) { 140 | clearTimeout(innerTimeoutId); 141 | 142 | // Animate the transition if it's enabled 143 | headlineContainer.fadeOut(opts.fadeOutSpeed, function () { 144 | // Now it's faded out, let's update the text 145 | headlineContainer.html(getCurrentTick()); 146 | // And fade in 147 | headlineContainer.fadeIn(opts.fadeInSpeed, function () { 148 | // Advance our position 149 | currentHeadlinePosition += 1; 150 | // And now we're in, let's start the thing off again without the delay 151 | innerTick(); 152 | }); 153 | }); 154 | } 155 | else { 156 | // Update the text 157 | headlineContainer.html(getCurrentTick()); 158 | // Advance our position 159 | currentHeadlinePosition += 1; 160 | clearTimeout(innerTimeoutId); 161 | innerTick(); 162 | } 163 | } 164 | 165 | // Get the current tick string 166 | function getCurrentTick() { 167 | var cursor, i, j, location; 168 | switch (currentHeadlinePosition % 2) { 169 | case 1: 170 | cursor = opts.cursorOne; 171 | break; 172 | case 0: 173 | cursor = opts.cursorTwo; 174 | break; 175 | } 176 | 177 | // Don't display the cursor this was the last character of the headline 178 | if (currentHeadlinePosition >= headlines[currentHeadline].length) cursor = ''; 179 | 180 | // Generate the headline 181 | var headline = ''; 182 | var openedTags = []; 183 | for (i = 0; i < currentHeadlinePosition; i++) { 184 | location = null; 185 | // Check to see if there's meant to be a tag at this index 186 | for (j = 0; j < headlineTagMap[currentHeadline].length; j++) { 187 | // Find a tag mapped to this location, if one exists 188 | if (headlineTagMap[currentHeadline][j] && headlineTagMap[currentHeadline][j].start === i) { 189 | location = headlineTagMap[currentHeadline][j]; // It does exist! 190 | break; 191 | } 192 | } 193 | 194 | if (location) { 195 | // Add the tag to the headline 196 | headline += location.tag; 197 | 198 | // Now deal with the tag for proper HTML 199 | if (! location.selfClosing) { 200 | if (location.tag.charAt(1) === '/') { 201 | openedTags.pop(); 202 | } 203 | else { 204 | openedTags.push(location.name); 205 | } 206 | } 207 | } 208 | 209 | // Add the character to the headline 210 | headline += headlines[currentHeadline][i]; 211 | } 212 | 213 | // Now close the tags, if we need to (because it hasn't finished with all the text in the tag) 214 | for (i = 0; i < openedTags.length; i++) { 215 | headline += ''; 216 | } 217 | 218 | return headline + cursor; 219 | } 220 | 221 | // Start it 222 | outerTick(); 223 | }); 224 | }; 225 | 226 | /** 227 | * Randomize array element order in-place. 228 | * Using Fisher-Yates shuffle algorithm. 229 | */ 230 | function shuffleArray(array) { 231 | for (var i = array.length - 1; i > 0; i--) { 232 | var j = Math.floor(Math.random() * (i + 1)); 233 | var temp = array[i]; 234 | array[i] = array[j]; 235 | array[j] = temp; 236 | } 237 | return array; 238 | } 239 | 240 | /** 241 | * Strip all HTML tags from a string. 242 | * An array of safe tags can be passed, which will not be 243 | * stripped from the string with the rest of the tags. 244 | */ 245 | function stripTags(text, safeTags) { 246 | safeTags = safeTags || []; 247 | var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/img; 248 | var comments = //img; 249 | return text.replace(comments, '').replace(tags, function (a, b) { 250 | return safeTags.indexOf(b.toLowerCase()) !== -1 ? a : ''; 251 | }); 252 | } 253 | 254 | /** 255 | * Locates all of the requested tags in a string. 256 | */ 257 | function locateTags(text, tagList) { 258 | tagList = tagList || []; 259 | var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/im; 260 | var selfClosing = /\/\s{0,}>$/m; 261 | var locations = []; 262 | var match, location; 263 | 264 | while ((match = tags.exec(text)) !== null) { 265 | if (tagList.length === 0 || tagList.indexOf(match[1]) !== -1) { 266 | location = { 267 | tag: match[0], 268 | name: match[1], 269 | selfClosing: selfClosing.test(match[0]), 270 | start: match.index, 271 | end: match.index + match[0].length - 1 272 | }; 273 | locations.push(location); 274 | 275 | // Now remove this tag from the string 276 | // so that each location will represent it in a string without any of the tags 277 | text = text.slice(0, location.start) + text.slice(location.end + 1); 278 | 279 | // Reset the regex 280 | tags.lastIndex = 0; 281 | } 282 | } 283 | 284 | return locations; 285 | } 286 | 287 | // Plugin default settings 288 | $.fn.ticker.defaults = { 289 | random: false, // Whether to display ticker items in a random order 290 | itemSpeed: 3000, // The pause on each ticker item before being replaced 291 | cursorSpeed: 50, // Speed at which the characters are typed 292 | pauseOnHover: true, // Whether to pause when the mouse hovers over the ticker 293 | finishOnHover: true, // Whether or not to complete the ticker item instantly when moused over 294 | cursorOne: '_', // The symbol for the first part of the cursor 295 | cursorTwo: '-', // The symbol for the second part of the cursor 296 | fade: true, // Whether to fade between ticker items or not 297 | fadeInSpeed: 600, // Speed of the fade-in animation 298 | fadeOutSpeed: 300 // Speed of the fade-out animation 299 | }; 300 | 301 | })(jQuery); 302 | --------------------------------------------------------------------------------