sp-supermarquee
Version:
Super smooth marquee library for your applications.
644 lines (566 loc) • 24.2 kB
JavaScript
import { getHorizontal, getVertical } from "./Template.js";
import { Configuration } from "./Configuration.js";
import { Event } from "./Event.js";
import { TickLogic } from "./TickLogic.js";
import { RssFeedReader } from "./RssFeedReader.js";
import { Util } from "./Util.js";
let hasLicenseTextBeenShown = false;
function Core( root, config )
{
const self = this,
events = new Event( this );
let fnTick = TickLogic.fnNoScroll.bind( this );
this.elems = {
rootElement : root,
shadowRoot : null,
container : null,
outerWrapper : null,
innerContainer : null,
scrollItem : null,
scrollItemClone : null
};
this.config = config;
this.dimensions = {};
this._uuid = Util.generateUUID();
this._time = Date.now();
this._rafId = null;
this._shouldPlay = false;
this._wasPlaying = false;
this._isInView = true;
this._previousDimensions = { width: this.elems.rootElement.offsetWidth, height: this.elems.rootElement.offsetHeight };
const listenerWindowResize = () =>
{
if ( this.elems.rootElement.offsetWidth === this._previousDimensions.width &&
this.elems.rootElement.offsetHeight === this._previousDimensions.height )
{
// Do nothing
return false;
}
else
{
this._previousDimensions = { width: this.elems.rootElement.offsetWidth, height: this.elems.rootElement.offsetHeight };
}
let wasRunning = false;
if ( this._rafId )
{
wasRunning = true;
window.cancelAnimationFrame( this._rafId );
this._rafId = null;
}
this.init();
if ( wasRunning )
{
this.tick();
}
},
listenerDocumentVisibilityChange = () =>
{
if ( true === document.hidden )
{
this.onOutOfView();
}
else
{
this.onIntoView();
}
},
listenerElemsContainerMouseEnter = () =>
{
if ( true === this.config.pauseonhover )
{
this.pause();
}
},
listenerElemsContainerMouseLeave = () =>
{
if ( true === this.config.pauseonhover )
{
this.play();
}
};
// Make sure scrolling is only active, if element is in viewport
let observer = null;
document.addEventListener( 'DOMContentLoaded', () =>
{
if ( !observer )
{
observer = new IntersectionObserver((entries, observer) => {
let entry = entries.length ? entries[ 0 ] : null;
if ( entry )
{
if ( true === entry.isIntersecting )
{
this.onIntoView();
}
else
{
this.onOutOfView();
}
}
}, {rootMargin: "0px 0px 20px 0px"});
observer.observe( self.elems.container );
}
});
setup();
this.init = function()
{
if ( this.config.cssClass && this.config.cssClass.length > 0 )
{
this.elems.rootElement.classList.add( this.config.cssClass );
}
this.elems.container.style.visibility = 'hidden';
let prevPosition = this.elems.rootElement.style.position;
this.elems.rootElement.style.position = 'initial';
this.elems.scrollItem.innerHTML = "";
this.elems.scrollItemClone.innerHTML = "";
let scrollContent = this.config.content;
if ( this.config.rssFeedUrl )
{
const feedItemData = RssFeedReader.getFeedItemData( this.config.rssFeedUrl );
if ( feedItemData && feedItemData.length > 0 )
{
let title, link, pubDate, description, content;
for ( let fi = 0; fi < feedItemData.length; fi++ )
{
title = feedItemData[ fi ].hasOwnProperty( 'title' ) && feedItemData[ fi ].title ? feedItemData[ fi ].title : '';
link = feedItemData[ fi ].hasOwnProperty( 'link' ) && feedItemData[ fi ][ 'link' ] ? feedItemData[ fi ][ 'link' ] : '#';
pubDate = feedItemData[ fi ].hasOwnProperty( 'pubDate' ) && feedItemData[ fi ].pubDate ? feedItemData[ fi ].pubDate : new Date();
description = feedItemData[ fi ].hasOwnProperty( 'description' ) && feedItemData[ fi ].description ? feedItemData[ fi ].description : '';
content = feedItemData[ fi ].hasOwnProperty( 'content' ) && feedItemData[ fi ].content ? feedItemData[ fi ].content : '';
scrollContent += eval( "`" + this.config.rssFeedTemplate + "`" );
scrollContent += this.config.spacer;
}
}
}
if ( Configuration.TYPE_HORIZONTAL === this.config.type )
{
let maxWidth;
// Add same content to clone item
switch ( this.config.mode )
{
case Configuration.MODE_PINGPONG:
this.elems.scrollItem.innerHTML += scrollContent;
this.elems.scrollItemClone.innerHTML = "";
maxWidth = ( prevPosition === 'fixed' ) ? window.innerWidth : this.elems.container.clientWidth;
this.config._gappedScrollingEnabled = ( maxWidth < this.elems.scrollItem.clientWidth );
break;
case Configuration.MODE_GAPPED:
this.elems.scrollItem.innerHTML += scrollContent;
this.elems.scrollItem.style.paddingLeft = this.elems.container.clientWidth + 'px';
this.elems.scrollItemClone.innerHTML = "";
break;
default:
case Configuration.MODE_CONTINOUS:
// Fill up the space of complete window
maxWidth = ( prevPosition === 'fixed' ) ? window.innerWidth : this.elems.container.clientWidth;
while ( maxWidth > this.elems.scrollItem.clientWidth )
{
this.elems.scrollItem.innerHTML += scrollContent;
}
this.elems.scrollItemClone.innerHTML = this.elems.scrollItem.innerHTML;
break;
}
}
// Vertical
else
{
if ( 0 <= this.elems.scrollItem.getBoundingClientRect().height )
{
this.elems.scrollItem.innerHTML = " ";
}
let maxHeight = ( prevPosition === 'fixed' ) ? window.innerHeight : this.elems.container.clientHeight;
this.elems.outerWrapper.style.maxHeight = maxHeight + 'px';
this.elems.scrollItem.innerHTML = "";
// Replace linebreaks with <br>-tags
scrollContent = scrollContent.replace(/(?:\r\n|\r|\n)/g, '<br />');
switch ( this.config.mode )
{
case Configuration.MODE_PINGPONG:
this.elems.scrollItem.innerHTML += scrollContent;
this.elems.scrollItemClone.innerHTML = "";
maxHeight = ( prevPosition === 'fixed' ) ? window.innerHeight : this.elems.container.clientHeight;
this.config._gappedScrollingEnabled = ( maxHeight < this.elems.scrollItem.clientHeight );
break;
case Configuration.MODE_GAPPED:
this.elems.scrollItem.innerHTML += scrollContent;
this.elems.scrollItem.style.paddingTop = this.elems.container.clientHeight + 'px';
this.elems.scrollItemClone.innerHTML = "";
break;
default:
case Configuration.MODE_CONTINOUS:
maxHeight = ( prevPosition === 'fixed' ) ? window.innerHeight : this.elems.container.clientHeight;
// Fill up the space of scroll container
while ( maxHeight > this.elems.scrollItem.clientHeight )
{
this.elems.scrollItem.innerHTML += scrollContent;
}
this.elems.scrollItemClone.innerHTML = this.elems.scrollItem.innerHTML;
break;
}
}
this.updatePerspective();
// Set dimensions
// Note:
// Timeout is required to make sure that dimensions calculations work as expected.
// Timeout delay needs to be in sync with SuperMarquee.setup timeout.
setTimeout( () =>
{
this.updateDimensions();
this.updateTickLogic();
this.elems.container.style.visibility = 'visible';
this.onIntoView();
}, 100 );
// Init
this._currentXPos = 0;
this._currentYPos = 0;
this._pingPongCurrentDirection = 0;
this._pingPongNextDirection = -1;
this._pingPongPauseDelay = self.config.pingPongDelay;
this._time = Date.now();
this.config.setPosition( this.config.position );
switch( this.config.position )
{
case Configuration.POSITION_FIXEDTOP:
this.elems.rootElement.style.position = 'fixed';
this.elems.rootElement.style.removeProperty( 'left' );
this.elems.rootElement.style.removeProperty( 'top' );
this.elems.rootElement.style.removeProperty( 'bottom' );
this.elems.rootElement.style.width = "100%";
this.elems.rootElement.style.top = 0;
this.elems.rootElement.style.bottom = 'initial';
this.elems.rootElement.style.left = 0;
break;
case Configuration.POSITION_FIXEDBOTTOM:
this.elems.rootElement.style.position = 'fixed';
this.elems.rootElement.style.removeProperty( 'left' );
this.elems.rootElement.style.removeProperty( 'bottom' );
this.elems.rootElement.style.width = "100%";
this.elems.rootElement.style.top = 'initial';
this.elems.rootElement.style.bottom = 0;
this.elems.rootElement.style.left = 0;
break;
default:
case Configuration.POSITION_CUSTOM:
this.elems.rootElement.style.position = prevPosition;
this.elems.rootElement.style.removeProperty( 'position' );
this.elems.rootElement.style.removeProperty( 'left' );
this.elems.rootElement.style.removeProperty( 'top' );
this.elems.rootElement.style.removeProperty( 'bottom' );
break;
}
this.setFader( this.config.fader );
// Hello
if ( false === this.config.hasLicense() && window.console && false === hasLicenseTextBeenShown )
{
hasLicenseTextBeenShown = true;
console.log( "%c»SuperMarquee« by SuperPlug.in. Unlicensed version for non commercial use only.", "font-family: monospace sans-serif; background-color: #8089ff; color: white;font-weight: bold;" );
}
}.bind ( this );
this.updatePerspective = function()
{
this.elems.perspective.style.perspective = this.config.perspective.z + 'px';
this.elems.perspective.style.perspectiveOrigin = this.config.perspective.originX + '% ' + this.config.perspective.originY + '%';
this.elems.outerWrapper.style.transform = 'rotateX(' + this.config.perspective.rotateX + 'deg) rotateY(' + this.config.perspective.rotateY + 'deg) rotateZ(' + this.config.perspective.rotateZ + 'deg)';
}.bind( this );
this.getInstanceId = function()
{
return this._uuid;
};
this.updateTickLogic = function()
{
if ( Configuration.MODE_PINGPONG === self.config.mode )
{
if ( false === self.config._gappedScrollingEnabled )
{
fnTick = TickLogic.fnNoScroll.bind( self );
}
else
{
if ( Configuration.TYPE_HORIZONTAL === self.config.type )
{
switch ( self.config.direction )
{
case Configuration.DIRECTION_RTL:
this._currentXPos = this.dimensions.totalScrollItemWidth - this.dimensions.visibleWidth;
this._pingPongCurrentDirection = 0;
this._pingPongNextDirection = -1;
this._pingPongPauseDelay = self.config.pingPongDelay;
fnTick = TickLogic.fnHorizontalRtlPingPong.bind( self );
break;
default:
case Configuration.DIRECTION_LTR:
this._currentXPos = 0;
this._pingPongCurrentDirection = 0;
this._pingPongNextDirection = 1;
this._pingPongPauseDelay = self.config.pingPongDelay;
fnTick = TickLogic.fnHorizontalLtrPingPong.bind( self );
break;
}
}
else
{
switch ( self.config.direction )
{
case Configuration.DIRECTION_TTB:
this._currentYPos = -(this.dimensions.totalScrollItemHeight - this.dimensions.visibleHeight);
this._pingPongCurrentDirection = 0;
this._pingPongNextDirection = -1;
this._pingPongPauseDelay = self.config.pingPongDelay;
fnTick = TickLogic.fnHorizontalTtbPingPong.bind( self );
break;
case Configuration.DIRECTION_BTT:
this._currentYPos = 0;
this._pingPongCurrentDirection = 0;
this._pingPongNextDirection = -1;
this._pingPongPauseDelay = self.config.pingPongDelay;
fnTick = TickLogic.fnHorizontalBttPingPong.bind( self );
break;
}
}
}
}
else
{
if ( Configuration.TYPE_HORIZONTAL === self.config.type )
{
switch ( self.config.direction )
{
case Configuration.DIRECTION_RTL:
fnTick = TickLogic.fnHorizontalRtl.bind( self );
break;
default:
case Configuration.DIRECTION_LTR:
fnTick = TickLogic.fnHorizontalLtr.bind( self );
break;
}
}
else
{
switch ( self.config.direction )
{
case Configuration.DIRECTION_TTB:
fnTick = TickLogic.fnVerticalTtb.bind( self );
break;
default:
case Configuration.DIRECTION_BTT:
fnTick = TickLogic.fnVerticalBtt.bind( self );
break;
}
}
}
}.bind( this );
this.updateDimensions = function()
{
// Set dimensions
this.dimensions.visibleWidth = this.elems.container.clientWidth;
this.dimensions.visibleHeight = this.elems.container.clientHeight;
this.dimensions.totalScrollItemWidth = this.elems.scrollItem.offsetWidth;
this.dimensions.totalScrollItemHeight = this.elems.scrollItem.offsetHeight;
}.bind( this );
this.tick = function()
{
const currentTime = Date.now();
const deltaTime = currentTime - this._time;
this._time = currentTime;
if ( this._shouldPlay )
{
fnTick( deltaTime );
}
this._rafId = window.requestAnimationFrame( this.tick );
}.bind ( this );
this.getRootElement = function()
{
return this.elems.rootElement;
}.bind( this );
this.getEvents = function()
{
return events;
};
this.destroy = function()
{
if ( this._rafId )
{
window.cancelAnimationFrame( this._rafId );
this._rafId = null;
}
window.removeEventListener( 'resize', listenerWindowResize );
document.removeEventListener( 'visibilitychange', listenerDocumentVisibilityChange );
this.elems.container.removeEventListener( 'mouseenter', listenerElemsContainerMouseEnter );
this.elems.container.removeEventListener( 'mouseleave', listenerElemsContainerMouseLeave );
if ( observer )
{
observer.unobserve( this.elems.container );
}
this.elems.rootElement.innerHTML = "";
};
this.getScrollContent = function()
{
return this.config.content;
};
function setup()
{
switch ( self.config.system )
{
case Configuration.SYSTEM_JQUERY:
case Configuration.SYSTEM_VANILLA:
case Configuration.SYSTEM_WEBCOMPONENT:
self.elems.shadowRoot = self.elems.rootElement;//self.elems.rootElement.attachShadow( { mode : 'open' } );
if ( self.config.type === Configuration.TYPE_VERTICAL )
{
const templ = getVertical( { instanceId : self._uuid } );
self.elems.shadowRoot.appendChild( templ.content.cloneNode( true ) );
//self.elems.shadowRoot.appendChild( templateVertical.content.cloneNode( true ) );
}
else
{
const templ = getHorizontal( { instanceId : self._uuid } );
self.elems.shadowRoot.appendChild( templ.content.cloneNode( true ) );
//self.elems.shadowRoot.appendChild( templ.cloneNode( true ) );
}
self.elems.container = self.elems.shadowRoot.querySelector( '[data-id="supermarquee-container"]' );
self.elems.perspective = self.elems.shadowRoot.querySelector( '[data-id="supermarquee-perspective"]:first-child' );
self.elems.outerWrapper = self.elems.shadowRoot.querySelector( '[data-id="supermarquee-outer-wrapper"]:first-child' );
self.elems.innerContainer = self.elems.shadowRoot.querySelector( '[data-id="supermarquee-inner-container"]:first-child' );
break;
}
self.elems.scrollItem = self.elems.innerContainer.querySelector( '[data-id="supermarquee-item"]:first-child' );
self.elems.scrollItemClone = self.elems.innerContainer.querySelector( '[data-id="supermarquee-item-clone"]' );
window.addEventListener( 'resize', listenerWindowResize );
document.addEventListener( 'visibilitychange', listenerDocumentVisibilityChange );
self.elems.container.addEventListener( 'mouseenter', listenerElemsContainerMouseEnter );
self.elems.container.addEventListener( 'mouseleave', listenerElemsContainerMouseLeave );
//observer.observe( self.elems.container );
}
}
Core.prototype.VERSION = "3.1";
Core.prototype.play = function()
{
this._shouldPlay = true;
if ( false === this._isInView ) return;
if ( !this._rafId )
{
this._time = Date.now();
this.tick();
this._wasPlaying = true;
}
};
Core.prototype.pause = function()
{
this._shouldPlay = false;
if ( this._rafId )
{
window.cancelAnimationFrame( this._rafId );
this._rafId = null;
this._wasPlaying = true;
}
else
{
this._wasPlaying = false;
}
};
Core.prototype.setPingPongDelay = function( pingPongDelay )
{
let ppd = +pingPongDelay;
if ( ppd <= 0 )
{
ppd = Configuration.PINGPONG_DELAY_DEFAULT;
}
this.config.pingPongDelay = ppd;
};
Core.prototype.setScrollContent = function( content )
{
this.config.setContent( content );
this.init();
};
Core.prototype.setScrollSpeed = function( speed )
{
this.config.setSpeed( speed );
};
Core.prototype.setPosition = function( position )
{
this.config.setPosition( position );
this.init();
};
Core.prototype.setPauseOnHover = function( pauseonhover )
{
this.config.setPauseOnHover( pauseonhover );
};
Core.prototype.setPerspective = function( perspective )
{
this.config.setPerspective( perspective );
this.updatePerspective();
};
Core.prototype.setFader = function( fader )
{
this.config.setFader( fader );
this.updateFader();
};
Core.prototype.updateFader = function()
{
const gradientDirections = {
"left" : "to right",
"right" : "to left",
"top" : "180deg",
"bottom" : "180deg"
}
const props = this.config.type === Configuration.TYPE_HORIZONTAL ? [ 'left', 'right' ] : [ 'top', 'bottom' ];
const fd = this.config.fader;
if ( this.config.type === Configuration.TYPE_HORIZONTAL )
{
this.elems.outerWrapper.classList.remove( `fader-top-${this.getInstanceId()}` );
this.elems.outerWrapper.classList.remove( `fader-bottom-${this.getInstanceId()}` );
}
else
{
this.elems.outerWrapper.classList.remove( `fader-left-${this.getInstanceId()}` );
this.elems.outerWrapper.classList.remove( `fader-right-${this.getInstanceId()}` );
}
for ( let pi = 0; pi < props.length; pi++ )
{
const prop = props[ pi ];
const propUp = String( prop ).charAt( 0 ).toUpperCase() + String( prop ).slice( 1 );
if ( fd[ prop ].size <= 0 )
{
// Remove class
this.elems.outerWrapper.classList.remove( 'fader-' + prop + '-' + this.getInstanceId() );
}
else
{
if ( this.config.mode === Configuration.MODE_PINGPONG && false === this.config._gappedScrollingEnabled )
{
// Do not show fading borders in PINGPONG mode when there is no need for scrolling
}
else
{
// Add class
this.elems.outerWrapper.classList.add( 'fader-' + prop + '-' + this.getInstanceId() );
this.elems.outerWrapper.style.setProperty( `--fader${propUp}`, `${fd[ prop ].size}%` );
// Set gradient
this.elems.outerWrapper.style.setProperty(
`--fader${propUp}Gradient`,
`linear-gradient( ${gradientDirections[ prop ]}, ${fd[ prop ].colorFrom}, ${fd[ prop ].colorTo})`
);
}
}
}
};
Core.prototype.onIntoView = function()
{
this._isInView = true;
// @todo Fix this
//if ( true === this._wasPlaying || true === this._shouldPlay )
{
this.play();
}
};
Core.prototype.onOutOfView = function()
{
this._isInView = false;
this.pause();
};
Core.prototype.destroy = function(){};
Core.prototype.Log = function( message, level = 'info' )
{
console[ level ]( message );
};
export { Core };