├── Marquee3G.js └── README.md /Marquee3G.js: -------------------------------------------------------------------------------- 1 | // A WORK IN PROGRESS 2 | class Marquee { 3 | constructor(options) { 4 | // ES6 this later 5 | this.element = options.element; 6 | this.gsap = options.gsap; 7 | this.originalContent = this.element.children[0]; 8 | this.destroy = this.destroy.bind(this); 9 | 10 | this.setInstanceBounds(); 11 | this.calculateRepetition(); 12 | this.createWrapper(); 13 | this.cloneNodes(); 14 | this.animate(); 15 | this.handleResize(); 16 | } 17 | 18 | 19 | // Rename vars---- too long 20 | setInstanceBounds() { 21 | // make sure we calculate the non-block bounding box 22 | this.originalContent.style.display = `inline-block`; 23 | 24 | this.bounds = { 25 | element: this.element.getBoundingClientRect(), 26 | content: this.element.querySelector('.marquee-container').getBoundingClientRect() 27 | }; 28 | } 29 | 30 | 31 | calculateRepetition() { 32 | const repetitions = (this.bounds.element.width + this.bounds.content.width) / this.bounds.content.width; 33 | this.repetitions = Math.ceil(repetitions); 34 | console.log(`Required repetitions: ${this.repetitions}`); 35 | } 36 | 37 | 38 | getChildren() { 39 | if (this.element.children.length > 1) { 40 | throw 'There is more than one child. Please only have one wrapper with `.marquee-container` selector.' 41 | } else { 42 | const elem = this.element.children[0]; 43 | elem.classList.add('marquee-copy'); 44 | return elem; 45 | } 46 | } 47 | 48 | 49 | createWrapper() { 50 | const wrapper = document.createElement('div'); 51 | wrapper.classList.add('marquee-wrapper'); 52 | wrapper.style.whiteSpace = 'nowrap'; 53 | this.element.appendChild(wrapper); 54 | this.element.style.overflow = 'hidden'; 55 | this.wrapper = wrapper; 56 | } 57 | 58 | 59 | cloneNodes(amount) { 60 | [...Array(amount || this.repetitions)].map((val, index) => { 61 | const clone = this.originalContent.cloneNode(true); 62 | this.wrapper.appendChild(clone); 63 | return clone; 64 | }); 65 | 66 | if (!amount) this.originalContent.remove(); 67 | } 68 | 69 | 70 | animate() { 71 | if (this.timeline) { 72 | this.timeline.kill(); 73 | } 74 | 75 | const tl = new this.gsap({ 76 | onComplete: () => { tl.restart(); } 77 | }); 78 | 79 | tl.set(this.wrapper, { x: 0 }); 80 | tl.to(this.wrapper, 2, { 81 | x: this.bounds.content.width * -1, 82 | force3D: true, 83 | ease: Power0.easeNone 84 | }); 85 | 86 | this.timeline = tl; 87 | } 88 | 89 | 90 | refresh() { 91 | const prevRepetitions = this.repetitions; 92 | 93 | this.calculateRepetition(); 94 | 95 | const diff = this.repetitions - prevRepetitions; 96 | if (diff > 0) this.cloneNodes(diff); 97 | 98 | this.setInstanceBounds(); 99 | this.animate(); 100 | } 101 | 102 | 103 | handleResize() { 104 | window.addEventListener('resize', debounce(() => { 105 | this.refresh(); 106 | }, 50)); 107 | } 108 | 109 | 110 | destroy() { 111 | this.timeline.kill(); 112 | this.element.children[0].remove(); 113 | this.element.append(this.originalContent); 114 | delete window.marquees[this.name || this.index]; 115 | } 116 | 117 | 118 | static init(options) { 119 | window.Marquees = {}; 120 | const elems = [...document.querySelectorAll(options.selector || '.marquee')]; 121 | 122 | elems.forEach((marquee, i) => { 123 | const instance = new Marquee({ 124 | element: marquee, 125 | gsap: options.gsap 126 | }); 127 | 128 | instance.index = i; 129 | window.Marquees[i] = instance; 130 | }); 131 | } 132 | } 133 | 134 | 135 | function debounce(func, wait, immediate) { 136 | let timeout; 137 | return function() { 138 | const context = this; 139 | const args = arguments; 140 | const later = function() { 141 | timeout = null; 142 | if (!immediate) func.apply(context, args); 143 | }; 144 | const callNow = immediate && !timeout; 145 | clearTimeout(timeout); 146 | timeout = setTimeout(later, wait); 147 | if (callNow) func.apply(context, args); 148 | }; 149 | }; 150 | 151 | 152 | export default Marquee; 153 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Marquee3G 2 | There's Marquee3K, but this one's `3G`. 3 | A Work in Progress™ but it's working. 4 | 5 | There's a vanila/non-GSAP version: [Marquee3000](https://github.com/ezekielaquino/Marquee3000) 6 | 7 | If you want to use it in its current state just do the following: 8 | 9 | **A Marquee's HTML must be structured like:** 10 | ```html 11 |