├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ └── publish.yml ├── update.json ├── info.ini ├── TODO.md ├── bower.json ├── package.json ├── marquee.jquery.json ├── licence ├── jquery.marquee.min.js ├── README.md └── jquery.marquee.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.idea -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [aamirafridi] 2 | -------------------------------------------------------------------------------- /update.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageManager": "github", 3 | "name": "jQuery.Marquee", 4 | "repo": "aamirafridi/jQuery.Marquee", 5 | "files": { 6 | "include": ["*.js"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /info.ini: -------------------------------------------------------------------------------- 1 | author = "Aamir Afridi" 2 | github = "https://github.com/aamirafridi/jQuery.Marquee" 3 | homepage = "http://aamirafridi.com/jquery/jquery-marquee-plugin" 4 | description = "jQuery plugin to scroll the text like the old traditional marquee, now with CSS3 animation support 😎" 5 | mainfile = "jquery.marquee.min.js" 6 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ### TODO list for myself and maintainers 2 | 3 | - move from http://aamirafridi.com/jquery/jquery-marquee-plugin to github pages 4 | - module support e.g. https://github.com/kenwheeler/slick/blob/master/slick/slick.js#L19-L26 5 | - check if we can remove the animation with javascript and just relay on CSS animations (check support) 6 | - review PRs 7 | - push the latest version on jsdelivr 8 | - [watch](https://help.github.jp/enterprise/2.11/user/articles/watching-and-unwatching-repositories/) this plugin and monitor issues 9 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - 'v*' # Triggers when you push a version tag, e.g., v1.0.0 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: 18 17 | registry-url: 'https://registry.npmjs.org' 18 | - run: npm ci 19 | - run: npm publish 20 | env: 21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery.marquee", 3 | "description": "jQuery plugin to scroll the text like the old traditional marquee http://aamirafridi.com/jquery/jquery-marquee-plugin", 4 | "main": "jquery.marquee.min.js", 5 | "authors": [ 6 | "aamirafridi@gmail.com" 7 | ], 8 | "license": "ISC", 9 | "keywords": [ 10 | "jquery-plugin", 11 | "ecosystem:jquery" 12 | ], 13 | "homepage": "https://github.com/aamirafridi/jQuery.Marquee", 14 | "moduleType": [], 15 | "ignore": [ 16 | "**/.*", 17 | "node_modules", 18 | "bower_components", 19 | "test", 20 | "tests" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery.marquee", 3 | "version": "1.6.1", 4 | "description": "jQuery plugin to scroll the text like the old traditional marquee http://aamirafridi.com/jquery/jquery-marquee-plugin — Edit", 5 | "main": "jquery.marquee.min.js", 6 | "scripts": { 7 | "minify": "uglifyjs jquery.marquee.js -o jquery.marquee.min.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/aamirafridi/jQuery.Marquee.git" 12 | }, 13 | "keywords": [ 14 | "jquery-plugin", 15 | "ecosystem:jquery", 16 | "jquery", 17 | "marquee", 18 | "css3", 19 | "animation" 20 | ], 21 | "author": "aamirafridi@gmail.com", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/aamirafridi/jQuery.Marquee/issues" 25 | }, 26 | "homepage": "https://github.com/aamirafridi/jQuery.Marquee#readme", 27 | "devDependencies": { 28 | "uglify-js": "^3.12.8" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /marquee.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marquee", 3 | "title": "jQuery Marquee with CSS3 Support", 4 | "description": "jQuery plugin to scroll the text like the old traditional marquee. NOW with CSS3 support.", 5 | "keywords": ["marquee", "animate", "scroll", "ticker", "news", "effect", "ui", "animation", "css3", "keyframes"], 6 | "version": "1.5.1", 7 | "author": { 8 | "name": "AamirAfridi.com", 9 | "email": "aamirafridi@gmail.com" 10 | }, 11 | "licenses": [ 12 | { 13 | "type": "MIT", 14 | "url": "http://opensource.org/licenses/MIT" 15 | } 16 | ], 17 | "dependencies": { 18 | "jquery": ">=1.3.0" 19 | }, 20 | "homepage": "http://aamirafridi.com/jquery/jquery-marquee-plugin", 21 | "docs": "https://github.com/aamirafridi/jQuery.Marquee/blob/master/README.md", 22 | "demo": "http://aamirafridi.com/jquery/jquery-marquee-plugin#examples", 23 | "download": "https://github.com/aamirafridi/jQuery.Marquee/archive/master.zip" 24 | } 25 | -------------------------------------------------------------------------------- /licence: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Aamir Afridi http://www.aamirafridi.com 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. 22 | -------------------------------------------------------------------------------- /jquery.marquee.min.js: -------------------------------------------------------------------------------- 1 | (function(e){"use strict";if(typeof define==="function"&&define.amd){define(["jquery"],e)}else if(typeof exports!=="undefined"){module.exports=e(require("jquery"))}else{e(jQuery)}})(function(X){X.fn.marquee=function(B){return this.each(function(){var i=X.extend({},X.fn.marquee.defaults,B),a=X(this),r,n,s,o,u,f=3,e="animation-play-state",d=false,l=function(e,t,i){var a=["webkit","moz","MS","o",""];for(var r=0;r');var h=a.find(".js-marquee").css({"margin-right":i.gap,float:"left"});if(i.duplicated){if(i.duplicateCount<=0){i.duplicateCount=1}for(let e=0;e');r=a.find(".js-marquee-wrapper");if(o){var y=a.height();r.removeAttr("style");a.height(y);a.find(".js-marquee").css({float:"none","margin-bottom":i.gap,"margin-right":0});if(i.duplicated){a.find(".js-marquee:last").css({"margin-bottom":0})}var v=a.find(".js-marquee:first").height()+i.gap;if(i.startVisible&&!i.duplicated){i._completeDuration=(parseInt(v,10)+parseInt(y,10))/parseInt(y,10)*i.duration;i.duration=parseInt(v,10)/parseInt(y,10)*i.duration}else{i.duration=(parseInt(v,10)+parseInt(y,10))/parseInt(y,10)*i.duration}}else{u=a.find(".js-marquee:first").width()+i.gap;n=a.width();if(i.startVisible&&!i.duplicated){i._completeDuration=(parseInt(u,10)+parseInt(n,10))/parseInt(n,10)*i.duration;i.duration=parseInt(u,10)/parseInt(n,10)*i.duration}else{i.duration=(parseInt(u,10)+parseInt(n,10))/parseInt(n,10)*i.duration}}if(i.duplicated){i.duration=i.duration/2}if(i.allowCss3Support){var x=document.body||document.createElement("div"),I="marqueeAnimation-"+Math.floor(Math.random()*1e7),w="Webkit Moz O ms Khtml".split(" "),S="animation",b="",q="";if(x.style.animation!==undefined){q="@keyframes "+I+" ";d=true}if(d===false){for(var j=0;j2){r.css("transform","translateY("+(i.direction==="up"?0:"-"+v+"px")+")")}s={transform:"translateY("+(i.direction==="up"?"-"+v+"px":0)+")"}}else if(i.startVisible){if(f===2){if(b){b=I+" "+i.duration/1e3+"s "+i.delayBeforeStart/1e3+"s "+i.css3easing}s={transform:"translateY("+(i.direction==="up"?"-"+v+"px":y+"px")+")"};f++}else if(f===3){i.duration=i._completeDuration;if(b){I=I+"0";q=X.trim(q)+"0 ";b=I+" "+i.duration/1e3+"s 0s infinite "+i.css3easing}V()}}else{V();s={transform:"translateY("+(i.direction==="up"?"-"+r.height()+"px":y+"px")+")"}}}else{if(i.duplicated){if(f>2){r.css("transform","translateX("+(i.direction==="left"?0:"-"+u+"px")+")")}s={transform:"translateX("+(i.direction==="left"?"-"+u+"px":0)+")"}}else if(i.startVisible){if(f===2){if(b){b=I+" "+i.duration/1e3+"s "+i.delayBeforeStart/1e3+"s "+i.css3easing}s={transform:"translateX("+(i.direction==="left"?"-"+u+"px":n+"px")+")"};f++}else if(f===3){i.duration=i._completeDuration;if(b){I=I+"0";q=X.trim(q)+"0 ";b=I+" "+i.duration/1e3+"s 0s infinite "+i.css3easing}k()}}else{k();s={transform:"translateX("+(i.direction==="left"?"-"+u+"px":n+"px")+")"}}}a.trigger("beforeStarting");if(d){r.css(S,b);var e=q+" { 100% "+p(s)+"}",t=r.find("style");if(t.length!==0){t.filter(":last").html(e)}else{X("head").append("")}l(r[0],"AnimationIteration",function(){a.trigger("finished")});l(r[0],"AnimationEnd",function(){A();a.trigger("finished")})}else{r.animate(s,i.duration,i.easing,function(){a.trigger("finished");if(i.pauseOnCycle){c()}else{A()}})}a.data("runningStatus","resumed")};a.on("pause",t.pause);a.on("resume",t.resume);if(i.pauseOnHover){a.on("mouseenter",t.pause);a.on("mouseleave",t.resume)}if(d&&i.allowCss3Support){A()}else{c()}})};X.fn.marquee.defaults={allowCss3Support:true,css3easing:"linear",easing:"linear",delayBeforeStart:1e3,direction:"left",duplicated:false,duplicateCount:1,duration:5e3,speed:0,gap:20,pauseOnCycle:false,pauseOnHover:false,startVisible:false}}); 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery-Marquee with CSS3 Support 2 | 3 | [![Known Vulnerabilities](https://snyk.io/test/github/aamirafridi/jquery.marquee/badge.svg?targetFile=package.json)](https://snyk.io/test/github/aamirafridi/jquery.marquee?targetFile=package.json) 4 | [![](https://data.jsdelivr.com/v1/package/npm/jquery.marquee/badge)](https://www.jsdelivr.com/package/npm/jquery.marquee) 5 | 6 | A small jQuery plugin to scroll the text like the old traditional marquee. 7 | 8 | Install: 9 | ---- 10 | - **NPM:** `npm install jquery.marquee --save` 11 | - **CDN:** [jsdelivr.com](http://www.jsdelivr.com/#!jquery.marquee) 12 | ```html 13 | 15 | ``` 16 | - **Bower**: `bower install jQuery.Marquee` 17 | - **Download:** [zip](https://github.com/aamirafridi/jQuery.Marquee/archive/master.zip) 18 | 19 | Links: 20 | ----- 21 | - **jsFiddle:** http://jsfiddle.net/aamir/jc7F3/285/ to help you explain any issues you might face. 22 | 23 | Options: 24 | -------- 25 | - **allowCss3Support** Force the usage of jQuery's animate method even if the target browser supports CSS3 animations. Default: ```true``` 26 | - **css3easing** Works when ```allowCss3Support``` is set to ```true```. See [here](http://www.w3.org/TR/2013/WD-css3-transitions-20131119/#transition-timing-function) for full list. Default: ```'linear'``` 27 | - **easing** Requires the jQuery [easing](http://gsgd.co.uk/sandbox/jquery/easing/) plugin. Default: ```'linear'``` 28 | - **delayBeforeStart** Time in milliseconds before the marquee starts animating. Default: ```1000``` 29 | - **direction** Direction towards which the marquee will animate ```'left' / 'right' / 'up' / 'down'```. Default: ```'left'```. Todo: need to change this to ```ltr/rtl``` etc 30 | - **duplicated** Should the marquee be duplicated to show an effect of continuous flow. Use this only when the text is shorter than the container. Default: ```false``` 31 | - **duplicateCount** The number of duplicates to be added. Default: ```1``` 32 | - **duration** Duration in milliseconds in which you want your element to travel. Default: ```5000```. 33 | - **speed** Speed will override duration. Speed allows you to set a relatively constant marquee speed regardless of the width of the containing element. Speed is measured in pixels per second. 34 | - **gap** Gap in pixels between the tickers. Will work only when the ```duplicated``` option is set to ```true```. Default: ```20```. Note: ```20``` means ```20px``` so no need to use ```'20px'``` as the value. 35 | - **pauseOnHover** Pause the marquee on hover. If the browser supports CSS3 and ```allowCss3Support``` is set to ```true``` this will be done using CSS3. Otherwise this will be done using the jQuery [Pause](https://github.com/tobia/Pause) plugin. Default: ```false```. See demo page for example. 36 | - **pauseOnCycle** On cycle, pause the marquee for ```delayBeforeStart``` milliseconds. 37 | - **startVisible** The marquee will be visible from the start if set to `true`. Thanks to @nuke-ellington 👍 38 | 39 | Events: 40 | ------ 41 | - **beforeStarting:** Event will be fired on the element before animation starts. 42 | - **finished:** Event will be fired on the element after the animation finishes. 43 | - **paused:** Event will be fired on the element when the animation is paused. 44 | - **resumed:** Event will be fired on the element when the animation is resumed. 45 | 46 | Methods: 47 | --------------- 48 | 49 | These methods can be used like this: 50 | 51 | - First initialize marquee with any options ``` var $mq = $('.marquee').marquee();``` 52 | - Then at any time you can call the following methods like this: ```var $mq.marquee('NAME-OF-METHOD-AS-STRING');``` 53 | 54 | Here is the list of all methods: 55 | 56 | - **pause**: To pause the marquee at any time. 57 | - **resume**: To resume the marquee after being paused previously. 58 | - **toggle**: To toggle between pause and resume methods. 59 | - **destroy**: To remove the marquee from your element. This method is useful if you are loading/changing the data using Ajax or just another string. You can combine this with the ```finished``` event so you can have the marquee show some data and as soon as it finishes showing that, you can destroy it, change the html and then apply the plugin again. See the demo page for details (links provided above). 60 | 61 | Usage: 62 | ---- 63 | ### Requiring in The Node.js Environment 64 | Here's how to import the plugin as a CommonJS module: 65 | 66 | ```javascript 67 | var $ = require("jquery"); 68 | require("jquery.marquee"); 69 | ``` 70 | 71 | ### HTML: 72 | 73 | Usually you assign the marquee class to the desired element. Then you initialize it with an options hash in your code (see below). 74 | 75 | ```html 76 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit END.
77 | ``` 78 | 79 | Alternatively you can provide all the options listed above as data attributes: 80 | 81 | ```html 82 |
83 | Lorem ipsum dolor sit amet, consectetur adipiscing elit END. 84 |
85 | ``` 86 | 87 | ### CSS: 88 | ```css 89 | .marquee { 90 | width: 300px; /* the plugin works for responsive layouts so width is not necessary */ 91 | overflow: hidden; 92 | border:1px solid #ccc; 93 | } 94 | ``` 95 | 96 | ### How to Apply Plugin: 97 | ```javascript 98 | /** 99 | * Example of starting a plugin with options. 100 | * I am just passing some of the options in the following example. 101 | * you can also start the plugin using $('.marquee').marquee(); with defaults 102 | */ 103 | $('.marquee').marquee({ 104 | //duration in milliseconds of the marquee 105 | duration: 15000, 106 | //gap in pixels between the tickers 107 | gap: 50, 108 | //time in milliseconds before the marquee will start animating 109 | delayBeforeStart: 0, 110 | //'left' or 'right' 111 | direction: 'left', 112 | //true or false - should the marquee be duplicated to show an effect of continues flow 113 | duplicated: true, 114 | //duplicate the message three times 115 | duplicateCount: 3 116 | }); 117 | ``` 118 | 119 | ### How to Use in a React Component (Class-Based) 120 | ```jsx 121 | import React, { Component } from 'react'; 122 | import $ from 'jquery'; 123 | import 'jquery.marquee'; 124 | 125 | class Marquee extends Component { 126 | componentDidMount() { 127 | this.$el.marquee({ 128 | duration: 15000, 129 | gap: 50, 130 | delayBeforeStart: 0, 131 | direction: 'left' 132 | }); 133 | } 134 | 135 | render() { 136 | return ( 137 |
this.$el = $(el)}> 138 | I'm using jQuery.Marquee with React!!!! 139 |
140 | ); 141 | } 142 | } 143 | ``` 144 | 145 | ### How to Use in a React Component (Functional) 146 | ```jsx 147 | import React, { useEffect, useRef } from 'react'; 148 | import $ from 'jquery'; 149 | import 'jquery.marquee'; 150 | 151 | function Marquee(props) { 152 | const el = useRef(); 153 | 154 | useEffect(function() { 155 | const $el = $(el.current); 156 | 157 | $el.marquee({ 158 | duration: 5000, 159 | gap: 50, 160 | delayBeforeStart: 0, 161 | direction: 'left' 162 | }); 163 | }); 164 | 165 | return ( 166 |
167 | I'm using jQuery.Marquee with React!!!! 168 |
169 | ); 170 | } 171 | ``` 172 | 173 | ### How to Use Methods: 174 | 175 | ```javascript 176 | var $mq = $('.marquee').marquee(); 177 | $('.someLink').click(function(){ 178 | $mq.marquee('pause/resume/toggle'); 179 | }); 180 | ``` 181 | 182 | Change content and re-apply the plugin. 183 | 184 | ```javascript 185 | $('.marquee') 186 | .bind('finished', function(){ 187 | //Change text to something else after first loop finishes 188 | $(this).marquee('destroy'); 189 | //Load new content using Ajax and update the marquee container 190 | $(this).html('Some new data loaded using ajax') 191 | //Apply marquee plugin again 192 | .marquee() 193 | }) 194 | .marquee(); 195 | 196 | ``` 197 | 198 | ### How to Use Events: 199 | 200 | ```javascript 201 | $('.marquee') 202 | .bind('beforeStarting', function () { 203 | //code you want to execute before starting the animations 204 | }) 205 | .bind('finished', function () { 206 | //code you want to execute before after each animation loop 207 | }) 208 | //Apply plugin 209 | .marquee({ 210 | duration: 2000 211 | }); 212 | ``` 213 | 214 | --- 215 | 216 | Images: 217 | ------ 218 | If you are using images in marquee, sometimes the plugin cannot calculate accurate widths while images are still loading. You can try this instead of ```$(document).ready(function(){...})``` 219 | 220 | ```javascript 221 | //if you have images in marquee 222 | $(window).load(function() { 223 | $('.marquee').marquee(); 224 | }); 225 | ``` 226 | ---- 227 | 228 | 229 | Updates: 230 | ----------- 231 | 232 | **Update (2 Jan 2025):** 233 | Added the `duplicateCount` parameter to control the number of duplicates that will be added when `duplicate` is `true`. Thanks to @tip2tail. 234 | 235 | **Update (8 Mar 2016):** 236 | Now plugin have new option: **startVisible** The marquee will be visible in the start if set to `true`. Thanks to @nuke-ellington 👍 237 | 238 | **Update (24 Jan 2014):** 239 | 240 | **Note: people who been asking me how to use this plugin with content being loaded with Ajax, please read notes about this update.** 241 | 242 | - New methods added, so now after you start the plugin using ```var $mq = $('.marquee').marquee();```, then you can pause, resume, toggle(pause, resume) and destroy methods e.g to remove the marquee plugin from your element simply use ```$mq.marquee('destroy');```. Similarly you can use pause the marquee any time using ```$mq.marquee('pause');```. 243 | - If you want to use use **Ajax** with this plugin i.e you want to change the content after you apply this plugin, please see the examples in demo page (link provided below). 244 | 245 | - Also made some changes so this plugin works with old versions of jQuery. I have tested it with jQuery 1.3.2 but quite sure it should work with some previous versions of jQuery. 246 | 247 | **PLEASE report any bugs that you may find.** 248 | 249 | **Update (20 Dec 2013):** 250 | Now the plugin will detect if browser supports CSS3 animations than it will animate the element using CSS3 which will perform much better than animating using jQuery. 251 | 252 | The ```pauseOnHover``` also works using CSS3. The plugin just prepares the setup and required CSS3 animation CSS. 253 | 254 | Due to some reasons if you want plugin to animate always using jQuery than you need to set ```allowCss3Support``` option to ```false```. Also an extra option ```css3easing``` is added. 255 | 256 | **Update (27 Nov 2013):** 257 | Easing option added. Requires jQuery easing plugin. 258 | 259 | 260 | **Update (22 Nov 2013):** 261 | Now plugin supports the 'up' and 'down' directions. Please have a look at the example to see how to use. 262 | 263 | 264 | **Update (21 Aug 2013):** 265 | If you want to hide the marquee for certain devices, try using ``` visibility: hidden``` with ``` height: 0``` & ```position: absolute``` instead of ``` display: none``` because jQuery cannot calculate with width etc of hidden elements. 266 | For more details: 267 | - https://github.com/aamirafridi/jQuery.Marquee/issues/9 268 | - http://stackoverflow.com/questions/1841124/find-the-potential-width-of-a-hidden-element 269 | 270 | 271 | **Update (22 Feb 2013):** 272 | ```pauseOnHover``` option added. Please note that you will need to include jQuery pause plugin: https://github.com/tobia/Pause before the jQuery Marquee plugin. 273 | 274 | 275 | **Update (20 Feb 2013):** 276 | - The plugin is improved to adjust the speed according to the length of the text automatically. For more details read: https://github.com/aamirafridi/jQuery.Marquee/issues/1 277 | - 'duplicated' option added. See the details below in Options section. 278 | -------------------------------------------------------------------------------- /jquery.marquee.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery.marquee - scrolling text like old marquee element 3 | * @author Aamir Afridi - aamirafridi(at)gmail(dot)com / http://aamirafridi.com/jquery/jquery-marquee-plugin 4 | */ 5 | ;(function(factory) { 6 | 'use strict'; 7 | if (typeof define === 'function' && define.amd) { 8 | define(['jquery'], factory); 9 | } else if (typeof exports !== 'undefined') { 10 | module.exports = factory(require('jquery')); 11 | } else { 12 | factory(jQuery); 13 | } 14 | })(function($) { 15 | $.fn.marquee = function(options) { 16 | return this.each(function() { 17 | // Extend the options if any provided 18 | var o = $.extend({}, $.fn.marquee.defaults, options), 19 | $this = $(this), 20 | $marqueeWrapper, containerWidth, animationCss, verticalDir, elWidth, 21 | loopCount = 3, 22 | playState = 'animation-play-state', 23 | css3AnimationIsSupported = false, 24 | 25 | // Private methods 26 | _prefixedEvent = function(element, type, callback) { 27 | var pfx = ["webkit", "moz", "MS", "o", ""]; 28 | for (var p = 0; p < pfx.length; p++) { 29 | if (!pfx[p]) type = type.toLowerCase(); 30 | element.addEventListener(pfx[p] + type, callback, false); 31 | } 32 | }, 33 | 34 | _objToString = function(obj) { 35 | var tabjson = []; 36 | for (var p in obj) { 37 | if (obj.hasOwnProperty(p)) { 38 | tabjson.push(p + ':' + obj[p]); 39 | } 40 | } 41 | tabjson.push(); 42 | return '{' + tabjson.join(',') + '}'; 43 | }, 44 | 45 | _startAnimationWithDelay = function() { 46 | $this.timer = setTimeout(animate, o.delayBeforeStart); 47 | }, 48 | 49 | // Public methods 50 | methods = { 51 | pause: function() { 52 | if (css3AnimationIsSupported && o.allowCss3Support) { 53 | $marqueeWrapper.css(playState, 'paused'); 54 | } else { 55 | // pause using pause plugin 56 | if ($.fn.pause) { 57 | $marqueeWrapper.pause(); 58 | } 59 | } 60 | // save the status 61 | $this.data('runningStatus', 'paused'); 62 | // fire event 63 | $this.trigger('paused'); 64 | }, 65 | 66 | resume: function() { 67 | // resume using css3 68 | if (css3AnimationIsSupported && o.allowCss3Support) { 69 | $marqueeWrapper.css(playState, 'running'); 70 | } else { 71 | // resume using pause plugin 72 | if ($.fn.resume) { 73 | $marqueeWrapper.resume(); 74 | } 75 | } 76 | // save the status 77 | $this.data('runningStatus', 'resumed'); 78 | // fire event 79 | $this.trigger('resumed'); 80 | }, 81 | 82 | toggle: function() { 83 | methods[$this.data('runningStatus') === 'resumed' ? 'pause' : 'resume'](); 84 | }, 85 | 86 | destroy: function() { 87 | // Clear timer 88 | clearTimeout($this.timer); 89 | // Unbind all events 90 | $this.find("*").addBack().off(); 91 | // Just unwrap the elements that has been added using this plugin 92 | $this.html($this.find('.js-marquee:first').html()); 93 | } 94 | }; 95 | 96 | // Check for methods 97 | if (typeof options === 'string') { 98 | if ($.isFunction(methods[options])) { 99 | // Following two IF statements to support public methods 100 | if (!$marqueeWrapper) { 101 | $marqueeWrapper = $this.find('.js-marquee-wrapper'); 102 | } 103 | if ($this.data('css3AnimationIsSupported') === true) { 104 | css3AnimationIsSupported = true; 105 | } 106 | methods[options](); 107 | } 108 | return; 109 | } 110 | 111 | /* Check if element has data attributes. They have top priority 112 | For details https://twitter.com/aamirafridi/status/403848044069679104 - Can't find a better solution :/ 113 | jQuery 1.3.2 doesn't support $.data().KEY hence writting the following */ 114 | var dataAttributes = {}, 115 | attr; 116 | $.each(o, function(key) { 117 | // Check if element has this data attribute 118 | attr = $this.attr('data-' + key); 119 | if (typeof attr !== 'undefined') { 120 | // Now check if value is boolean or not 121 | switch (attr) { 122 | case 'true': 123 | attr = true; 124 | break; 125 | case 'false': 126 | attr = false; 127 | break; 128 | } 129 | o[key] = attr; 130 | } 131 | }); 132 | 133 | // Reintroduce speed as an option. It calculates duration as a factor of the container width 134 | // measured in pixels per second. 135 | if (o.speed) { 136 | o.duration = parseInt($this.width(), 10) / o.speed * 1000; 137 | } 138 | 139 | // Shortcut to see if direction is upward or downward 140 | verticalDir = o.direction === 'up' || o.direction === 'down'; 141 | 142 | // no gap if not duplicated 143 | o.gap = o.duplicated ? parseInt(o.gap) : 0; 144 | 145 | // wrap inner content into a div 146 | $this.wrapInner('
'); 147 | 148 | // Make copy of the element 149 | var $el = $this.find('.js-marquee').css({ 150 | 'margin-right': o.gap, 151 | 'float': 'left' 152 | }); 153 | 154 | if (o.duplicated) { 155 | if (o.duplicateCount <= 0) { 156 | // If duplication enabled then the duplicate count must be a positive number 157 | o.duplicateCount = 1; 158 | } 159 | for (let duplicateLoop = 0; duplicateLoop < o.duplicateCount; duplicateLoop++) { 160 | $el.clone(true).appendTo($this); 161 | } 162 | } 163 | 164 | // wrap both inner elements into one div 165 | $this.wrapInner('
'); 166 | 167 | // Save the reference of the wrapper 168 | $marqueeWrapper = $this.find('.js-marquee-wrapper'); 169 | 170 | // If direction is up or down, get the height of main element 171 | if (verticalDir) { 172 | var containerHeight = $this.height(); 173 | $marqueeWrapper.removeAttr('style'); 174 | $this.height(containerHeight); 175 | 176 | // Change the CSS for js-marquee element 177 | $this.find('.js-marquee').css({ 178 | 'float': 'none', 179 | 'margin-bottom': o.gap, 180 | 'margin-right': 0 181 | }); 182 | 183 | // Remove bottom margin from 2nd element if duplicated 184 | if (o.duplicated) { 185 | $this.find('.js-marquee:last').css({ 186 | 'margin-bottom': 0 187 | }); 188 | } 189 | 190 | var elHeight = $this.find('.js-marquee:first').height() + o.gap; 191 | 192 | // adjust the animation duration according to the text length 193 | if (o.startVisible && !o.duplicated) { 194 | // Compute the complete animation duration and save it for later reference 195 | // formula is to: (Height of the text node + height of the main container / Height of the main container) * duration; 196 | o._completeDuration = ((parseInt(elHeight, 10) + parseInt(containerHeight, 10)) / parseInt(containerHeight, 10)) * o.duration; 197 | 198 | // formula is to: (Height of the text node / height of the main container) * duration 199 | o.duration = (parseInt(elHeight, 10) / parseInt(containerHeight, 10)) * o.duration; 200 | } else { 201 | // formula is to: (Height of the text node + height of the main container / Height of the main container) * duration; 202 | o.duration = ((parseInt(elHeight, 10) + parseInt(containerHeight, 10)) / parseInt(containerHeight, 10)) * o.duration; 203 | } 204 | 205 | } else { 206 | // Save the width of the each element so we can use it in animation 207 | elWidth = $this.find('.js-marquee:first').width() + o.gap; 208 | 209 | // container width 210 | containerWidth = $this.width(); 211 | 212 | // adjust the animation duration according to the text length 213 | if (o.startVisible && !o.duplicated) { 214 | // Compute the complete animation duration and save it for later reference 215 | // formula is to: (Width of the text node + width of the main container / Width of the main container) * duration; 216 | o._completeDuration = ((parseInt(elWidth, 10) + parseInt(containerWidth, 10)) / parseInt(containerWidth, 10)) * o.duration; 217 | 218 | // (Width of the text node / width of the main container) * duration 219 | o.duration = (parseInt(elWidth, 10) / parseInt(containerWidth, 10)) * o.duration; 220 | } else { 221 | // formula is to: (Width of the text node + width of the main container / Width of the main container) * duration; 222 | o.duration = ((parseInt(elWidth, 10) + parseInt(containerWidth, 10)) / parseInt(containerWidth, 10)) * o.duration; 223 | } 224 | } 225 | 226 | // if duplicated then reduce the duration 227 | if (o.duplicated) { 228 | o.duration = o.duration / 2; 229 | } 230 | 231 | if (o.allowCss3Support) { 232 | var elm = document.body || document.createElement('div'), 233 | animationName = 'marqueeAnimation-' + Math.floor(Math.random() * 10000000), 234 | domPrefixes = 'Webkit Moz O ms Khtml'.split(' '), 235 | animationString = 'animation', 236 | animationCss3Str = '', 237 | keyframeString = ''; 238 | 239 | // Check css3 support 240 | if (elm.style.animation !== undefined) { 241 | keyframeString = '@keyframes ' + animationName + ' '; 242 | css3AnimationIsSupported = true; 243 | } 244 | 245 | if (css3AnimationIsSupported === false) { 246 | for (var i = 0; i < domPrefixes.length; i++) { 247 | if (elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) { 248 | var prefix = '-' + domPrefixes[i].toLowerCase() + '-'; 249 | animationString = prefix + animationString; 250 | playState = prefix + playState; 251 | keyframeString = '@' + prefix + 'keyframes ' + animationName + ' '; 252 | css3AnimationIsSupported = true; 253 | break; 254 | } 255 | } 256 | } 257 | 258 | if (css3AnimationIsSupported) { 259 | animationCss3Str = animationName + ' ' + o.duration / 1000 + 's ' + o.delayBeforeStart / 1000 + 's infinite ' + o.css3easing; 260 | $this.data('css3AnimationIsSupported', true); 261 | } 262 | } 263 | 264 | var _rePositionVertically = function() { 265 | $marqueeWrapper.css('transform', 'translateY(' + (o.direction === 'up' ? containerHeight + 'px' : '-' + elHeight + 'px') + ')'); 266 | }, 267 | _rePositionHorizontally = function() { 268 | $marqueeWrapper.css('transform', 'translateX(' + (o.direction === 'left' ? containerWidth + 'px' : '-' + elWidth + 'px') + ')'); 269 | }; 270 | 271 | // if duplicated option is set to true than position the wrapper 272 | if (o.duplicated) { 273 | if (verticalDir) { 274 | if (o.startVisible) { 275 | $marqueeWrapper.css('transform', 'translateY(0)'); 276 | } else { 277 | $marqueeWrapper.css('transform', 'translateY(' + (o.direction === 'up' ? containerHeight + 'px' : '-' + ((elHeight * 2) - o.gap) + 'px') + ')'); 278 | } 279 | } else { 280 | if (o.startVisible) { 281 | $marqueeWrapper.css('transform', 'translateX(0)'); 282 | } else { 283 | $marqueeWrapper.css('transform', 'translateX(' + (o.direction === 'left' ? containerWidth + 'px' : '-' + ((elWidth * 2) - o.gap) + 'px') + ')'); 284 | } 285 | } 286 | 287 | // If the text starts out visible we can skip the two initial loops 288 | if (!o.startVisible) { 289 | loopCount = 1; 290 | } 291 | } else if (o.startVisible) { 292 | // We only have two different loops if marquee is duplicated and starts visible 293 | loopCount = 2; 294 | } else { 295 | if (verticalDir) { 296 | _rePositionVertically(); 297 | } else { 298 | _rePositionHorizontally(); 299 | } 300 | } 301 | 302 | // Animate recursive method 303 | var animate = function() { 304 | if (o.duplicated) { 305 | // When duplicated, the first loop will be scroll longer so double the duration 306 | if (loopCount === 1) { 307 | o._originalDuration = o.duration; 308 | if (verticalDir) { 309 | o.duration = o.direction === 'up' ? o.duration + (containerHeight / ((elHeight) / o.duration)) : o.duration * 2; 310 | } else { 311 | o.duration = o.direction === 'left' ? o.duration + (containerWidth / ((elWidth) / o.duration)) : o.duration * 2; 312 | } 313 | // Adjust the css3 animation as well 314 | if (animationCss3Str) { 315 | animationCss3Str = animationName + ' ' + o.duration / 1000 + 's ' + o.delayBeforeStart / 1000 + 's ' + o.css3easing; 316 | } 317 | loopCount++; 318 | } 319 | // On 2nd loop things back to normal, normal duration for the rest of animations 320 | else if (loopCount === 2) { 321 | o.duration = o._originalDuration; 322 | // Adjust the css3 animation as well 323 | if (animationCss3Str) { 324 | animationName = animationName + '0'; 325 | keyframeString = $.trim(keyframeString) + '0 '; 326 | animationCss3Str = animationName + ' ' + o.duration / 1000 + 's 0s infinite ' + o.css3easing; 327 | } 328 | loopCount++; 329 | } 330 | } 331 | 332 | if (verticalDir) { 333 | if (o.duplicated) { 334 | 335 | // Adjust the starting point of animation only when first loops finishes 336 | if (loopCount > 2) { 337 | $marqueeWrapper.css('transform', 'translateY(' + (o.direction === 'up' ? 0 : '-' + elHeight + 'px') + ')'); 338 | } 339 | 340 | animationCss = { 341 | 'transform': 'translateY(' + (o.direction === 'up' ? '-' + elHeight + 'px' : 0) + ')' 342 | }; 343 | } else if (o.startVisible) { 344 | // This loop moves the marquee out of the container 345 | if (loopCount === 2) { 346 | // Adjust the css3 animation as well 347 | if (animationCss3Str) { 348 | animationCss3Str = animationName + ' ' + o.duration / 1000 + 's ' + o.delayBeforeStart / 1000 + 's ' + o.css3easing; 349 | } 350 | animationCss = { 351 | 'transform': 'translateY(' + (o.direction === 'up' ? '-' + elHeight + 'px' : containerHeight + 'px') + ')' 352 | }; 353 | loopCount++; 354 | } else if (loopCount === 3) { 355 | // Set the duration for the animation that will run forever 356 | o.duration = o._completeDuration; 357 | // Adjust the css3 animation as well 358 | if (animationCss3Str) { 359 | animationName = animationName + '0'; 360 | keyframeString = $.trim(keyframeString) + '0 '; 361 | animationCss3Str = animationName + ' ' + o.duration / 1000 + 's 0s infinite ' + o.css3easing; 362 | } 363 | _rePositionVertically(); 364 | } 365 | } else { 366 | _rePositionVertically(); 367 | animationCss = { 368 | 'transform': 'translateY(' + (o.direction === 'up' ? '-' + ($marqueeWrapper.height()) + 'px' : containerHeight + 'px') + ')' 369 | }; 370 | } 371 | } else { 372 | if (o.duplicated) { 373 | 374 | // Adjust the starting point of animation only when first loops finishes 375 | if (loopCount > 2) { 376 | $marqueeWrapper.css('transform', 'translateX(' + (o.direction === 'left' ? 0 : '-' + elWidth + 'px') + ')'); 377 | } 378 | 379 | animationCss = { 380 | 'transform': 'translateX(' + (o.direction === 'left' ? '-' + elWidth + 'px' : 0) + ')' 381 | }; 382 | 383 | } else if (o.startVisible) { 384 | // This loop moves the marquee out of the container 385 | if (loopCount === 2) { 386 | // Adjust the css3 animation as well 387 | if (animationCss3Str) { 388 | animationCss3Str = animationName + ' ' + o.duration / 1000 + 's ' + o.delayBeforeStart / 1000 + 's ' + o.css3easing; 389 | } 390 | animationCss = { 391 | 'transform': 'translateX(' + (o.direction === 'left' ? '-' + elWidth + 'px' : containerWidth + 'px') + ')' 392 | }; 393 | loopCount++; 394 | } else if (loopCount === 3) { 395 | // Set the duration for the animation that will run forever 396 | o.duration = o._completeDuration; 397 | // Adjust the css3 animation as well 398 | if (animationCss3Str) { 399 | animationName = animationName + '0'; 400 | keyframeString = $.trim(keyframeString) + '0 '; 401 | animationCss3Str = animationName + ' ' + o.duration / 1000 + 's 0s infinite ' + o.css3easing; 402 | } 403 | _rePositionHorizontally(); 404 | } 405 | } else { 406 | _rePositionHorizontally(); 407 | animationCss = { 408 | 'transform': 'translateX(' + (o.direction === 'left' ? '-' + elWidth + 'px' : containerWidth + 'px') + ')' 409 | }; 410 | } 411 | } 412 | 413 | // fire event 414 | $this.trigger('beforeStarting'); 415 | 416 | // If css3 support is available than do it with css3, otherwise use jQuery as fallback 417 | if (css3AnimationIsSupported) { 418 | // Add css3 animation to the element 419 | $marqueeWrapper.css(animationString, animationCss3Str); 420 | var keyframeCss = keyframeString + ' { 100% ' + _objToString(animationCss) + '}', 421 | $styles = $marqueeWrapper.find('style'); 422 | 423 | // Now add the keyframe animation to the marquee element 424 | if ($styles.length !== 0) { 425 | // Bug fixed for jQuery 1.3.x - Instead of using .last(), use following 426 | $styles.filter(":last").html(keyframeCss); 427 | } else { 428 | $('head').append(''); 429 | } 430 | 431 | // Animation iteration event 432 | _prefixedEvent($marqueeWrapper[0], "AnimationIteration", function() { 433 | $this.trigger('finished'); 434 | }); 435 | // Animation stopped 436 | _prefixedEvent($marqueeWrapper[0], "AnimationEnd", function() { 437 | animate(); 438 | $this.trigger('finished'); 439 | }); 440 | 441 | } else { 442 | // Start animating 443 | $marqueeWrapper.animate(animationCss, o.duration, o.easing, function() { 444 | // fire event 445 | $this.trigger('finished'); 446 | // animate again 447 | if (o.pauseOnCycle) { 448 | _startAnimationWithDelay(); 449 | } else { 450 | animate(); 451 | } 452 | }); 453 | } 454 | // save the status 455 | $this.data('runningStatus', 'resumed'); 456 | }; 457 | 458 | // bind pause and resume events 459 | $this.on('pause', methods.pause); 460 | $this.on('resume', methods.resume); 461 | 462 | if (o.pauseOnHover) { 463 | $this.on('mouseenter', methods.pause); 464 | $this.on('mouseleave', methods.resume); 465 | } 466 | 467 | // If css3 animation is supported than call animate method at once 468 | if (css3AnimationIsSupported && o.allowCss3Support) { 469 | animate(); 470 | } else { 471 | // Starts the recursive method 472 | _startAnimationWithDelay(); 473 | } 474 | 475 | }); 476 | }; // End of Plugin 477 | // Public: plugin defaults options 478 | $.fn.marquee.defaults = { 479 | // If you wish to always animate using jQuery 480 | allowCss3Support: true, 481 | // works when allowCss3Support is set to true - for full list see http://www.w3.org/TR/2013/WD-css3-transitions-20131119/#transition-timing-function 482 | css3easing: 'linear', 483 | // requires jQuery easing plugin. Default is 'linear' 484 | easing: 'linear', 485 | // pause time before the next animation turn in milliseconds 486 | delayBeforeStart: 1000, 487 | // 'left', 'right', 'up' or 'down' 488 | direction: 'left', 489 | // true or false - should the marquee be duplicated to show an effect of continues flow 490 | duplicated: false, 491 | // number of duplicates to create, default is 1 492 | duplicateCount: 1, 493 | // duration in milliseconds of the marquee in milliseconds 494 | duration: 5000, 495 | // Speed allows you to set a relatively constant marquee speed regardless of the width of the containing element. Speed is measured in pixels per second. 496 | speed: 0, 497 | // gap in pixels between the tickers 498 | gap: 20, 499 | // on cycle pause the marquee 500 | pauseOnCycle: false, 501 | // on hover pause the marquee - using jQuery plugin https://github.com/tobia/Pause 502 | pauseOnHover: false, 503 | // the marquee is visible initially positioned next to the border towards it will be moving 504 | startVisible: false 505 | }; 506 | }); 507 | --------------------------------------------------------------------------------