Initial commit: Digital signage system for transit departures, weather, and news ticker
This commit is contained in:
297
ticker.js
Normal file
297
ticker.js
Normal file
@@ -0,0 +1,297 @@
|
||||
/**
|
||||
* ticker.js - A modular ticker component for displaying RSS feed content
|
||||
* Fetches and displays content from an RSS feed in a scrolling ticker at the bottom of the page
|
||||
*/
|
||||
|
||||
class TickerManager {
|
||||
constructor(options = {}) {
|
||||
// Default options
|
||||
this.options = {
|
||||
elementId: 'ticker-container',
|
||||
scrollSpeed: 60, // Animation duration in seconds
|
||||
maxItems: 10, // Maximum number of items to display
|
||||
...options
|
||||
};
|
||||
|
||||
// Create ticker container immediately
|
||||
this.createTickerContainer();
|
||||
|
||||
// State
|
||||
this.items = [];
|
||||
this.isScrolling = false;
|
||||
this.animationFrameId = null;
|
||||
|
||||
// Initialize RSS manager
|
||||
this.rssManager = new RssManager();
|
||||
|
||||
// Subscribe to RSS updates
|
||||
this.rssManager.onUpdate(items => {
|
||||
this.items = items.slice(0, this.options.maxItems);
|
||||
this.updateTicker();
|
||||
});
|
||||
|
||||
// Initialize
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the ticker container
|
||||
*/
|
||||
createTickerContainer() {
|
||||
// Create container if it doesn't exist
|
||||
if (!document.getElementById(this.options.elementId)) {
|
||||
console.log('Creating ticker container');
|
||||
const container = document.createElement('div');
|
||||
container.id = this.options.elementId;
|
||||
container.className = 'ticker-container';
|
||||
|
||||
// Create ticker content
|
||||
const tickerContent = document.createElement('div');
|
||||
tickerContent.className = 'ticker-content';
|
||||
container.appendChild(tickerContent);
|
||||
|
||||
// Add to document
|
||||
document.body.appendChild(container);
|
||||
|
||||
// Add styles
|
||||
this.addTickerStyles();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the ticker
|
||||
*/
|
||||
init() {
|
||||
console.log('Initializing TickerManager...');
|
||||
|
||||
try {
|
||||
// Set initial scroll speed
|
||||
this.setScrollSpeed(this.options.scrollSpeed);
|
||||
|
||||
// Add initial loading message
|
||||
const tickerContent = document.querySelector(`#${this.options.elementId} .ticker-content`);
|
||||
if (tickerContent) {
|
||||
tickerContent.innerHTML = '<div class="ticker-item">Loading news...</div>';
|
||||
}
|
||||
|
||||
// Ensure ticker is visible
|
||||
const container = document.getElementById(this.options.elementId);
|
||||
if (container) {
|
||||
container.style.display = 'block';
|
||||
}
|
||||
|
||||
console.log('TickerManager initialized successfully');
|
||||
} catch (error) {
|
||||
console.error('Error initializing TickerManager:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add ticker styles
|
||||
*/
|
||||
addTickerStyles() {
|
||||
// Check if styles already exist
|
||||
if (!document.getElementById('ticker-styles')) {
|
||||
const styleElement = document.createElement('style');
|
||||
styleElement.id = 'ticker-styles';
|
||||
|
||||
// Define styles
|
||||
styleElement.textContent = `
|
||||
.ticker-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: linear-gradient(to right, #3c3b6e, #b22234, #ffffff);
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
height: 40px;
|
||||
z-index: 100;
|
||||
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
body.vertical .ticker-container {
|
||||
transform: rotate(90deg);
|
||||
transform-origin: left bottom;
|
||||
width: 100vh;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
body.vertical-reverse .ticker-container {
|
||||
transform: rotate(-90deg);
|
||||
transform-origin: right bottom;
|
||||
width: 100vh;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
body.upsidedown .ticker-container {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.ticker-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
white-space: nowrap;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.ticker-item {
|
||||
display: inline-block;
|
||||
padding: 0 30px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.ticker-item:nth-child(3n+1) {
|
||||
background-color: rgba(178, 34, 52, 0.7); /* Red */
|
||||
}
|
||||
|
||||
.ticker-item:nth-child(3n+2) {
|
||||
background-color: rgba(255, 255, 255, 0.7); /* White */
|
||||
color: #3c3b6e; /* Dark blue text for readability */
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.ticker-item:nth-child(3n+3) {
|
||||
background-color: rgba(60, 59, 110, 0.7); /* Blue */
|
||||
}
|
||||
|
||||
@keyframes ticker-scroll {
|
||||
0% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Animation direction for different orientations */
|
||||
body.vertical .ticker-content {
|
||||
animation-direction: reverse; /* Reverse for vertical to maintain readability */
|
||||
}
|
||||
|
||||
body.upsidedown .ticker-content {
|
||||
animation-direction: reverse; /* Reverse for upside down to maintain readability */
|
||||
}
|
||||
|
||||
/* Dark mode styles */
|
||||
body.dark-mode .ticker-container {
|
||||
background: linear-gradient(to right, #1a1a4f, #8b1a29, #e6e6e6);
|
||||
}
|
||||
|
||||
body.dark-mode .ticker-item:nth-child(3n+1) {
|
||||
background-color: rgba(139, 26, 41, 0.7); /* Darker Red */
|
||||
}
|
||||
|
||||
body.dark-mode .ticker-item:nth-child(3n+2) {
|
||||
background-color: rgba(230, 230, 230, 0.7); /* Off-White */
|
||||
color: #1a1a4f; /* Darker blue text */
|
||||
}
|
||||
|
||||
body.dark-mode .ticker-item:nth-child(3n+3) {
|
||||
background-color: rgba(26, 26, 79, 0.7); /* Darker Blue */
|
||||
}
|
||||
`;
|
||||
|
||||
// Add to document
|
||||
document.head.appendChild(styleElement);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the ticker scroll speed
|
||||
* @param {number} speed - Speed in seconds for one complete scroll cycle
|
||||
*/
|
||||
setScrollSpeed(speed) {
|
||||
this.options.scrollSpeed = speed;
|
||||
const container = document.getElementById(this.options.elementId);
|
||||
if (container) {
|
||||
// Reset animation by removing and re-adding content
|
||||
const content = container.querySelector('.ticker-content');
|
||||
if (content) {
|
||||
const clone = content.cloneNode(true);
|
||||
container.style.setProperty('--ticker-speed', `${speed}s`);
|
||||
content.remove();
|
||||
container.appendChild(clone);
|
||||
}
|
||||
console.log(`Ticker speed set to ${speed} seconds`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update ticker content
|
||||
*/
|
||||
updateTicker() {
|
||||
console.log('Updating ticker content...');
|
||||
const tickerContent = document.querySelector(`#${this.options.elementId} .ticker-content`);
|
||||
|
||||
if (tickerContent) {
|
||||
// Clear existing content
|
||||
tickerContent.innerHTML = '';
|
||||
|
||||
if (this.items.length === 0) {
|
||||
console.log('No items to display in ticker');
|
||||
const tickerItem = document.createElement('div');
|
||||
tickerItem.className = 'ticker-item';
|
||||
tickerItem.textContent = 'Loading news...';
|
||||
tickerContent.appendChild(tickerItem);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Adding ${this.items.length} items to ticker`);
|
||||
|
||||
// Add items
|
||||
this.items.forEach((item, index) => {
|
||||
const tickerItem = document.createElement('div');
|
||||
tickerItem.className = 'ticker-item';
|
||||
|
||||
// Create link if available, using displayText or falling back to title
|
||||
if (item.link) {
|
||||
const link = document.createElement('a');
|
||||
link.href = item.link;
|
||||
link.target = '_blank';
|
||||
link.textContent = item.displayText || item.title;
|
||||
link.style.color = 'inherit';
|
||||
link.style.textDecoration = 'none';
|
||||
tickerItem.appendChild(link);
|
||||
} else {
|
||||
tickerItem.textContent = item.displayText || item.title;
|
||||
}
|
||||
|
||||
tickerContent.appendChild(tickerItem);
|
||||
});
|
||||
|
||||
console.log('Ticker content updated successfully');
|
||||
|
||||
// Calculate total width of content
|
||||
const totalWidth = Array.from(tickerContent.children)
|
||||
.reduce((width, item) => width + item.offsetWidth, 0);
|
||||
|
||||
// Calculate animation duration based on content width
|
||||
const duration = Math.max(totalWidth / 100, this.options.scrollSpeed);
|
||||
|
||||
// Reset and start animation
|
||||
tickerContent.style.animation = 'none';
|
||||
tickerContent.offsetHeight; // Force reflow
|
||||
tickerContent.style.animation = `ticker-scroll ${duration}s linear infinite`;
|
||||
|
||||
console.log(`Animation duration set to ${duration}s based on content width ${totalWidth}px`);
|
||||
} else {
|
||||
console.error('Ticker content element not found');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export the TickerManager class for use in other modules
|
||||
window.TickerManager = TickerManager;
|
||||
Reference in New Issue
Block a user