@digital-nature-ltd/carbon-lite
Version:
A simple tool to reduce the carbon usage of your website by blanking out the screen after a period of inactivity
332 lines (272 loc) • 10.6 kB
text/typescript
import { CarbonLiteMessage } from './elements/CarbonLiteMessage'
import { CarbonLiteElement } from './elements/CarbonLiteElement'
import { CarbonLiteConfig } from './config/CarbonLiteConfig';
export default class CarbonLite {
initialised: boolean = false;
ignoreInteractions: boolean = false;
// objects
carbonLite: CarbonLiteElement = new CarbonLiteElement();
carbonLiteMessage: CarbonLiteMessage = new CarbonLiteMessage();
carbonLiteTimer: ReturnType<typeof setTimeout>|undefined = undefined;
carbonLiteMessageTimer: ReturnType<typeof setTimeout>|undefined = undefined;
// configurable
config: CarbonLiteConfig = {
message: 'CarbonLite is reducing the carbon impact of this site, one (dark) pixel at a time',
timeout: 60000,
backgroundColour: '#000',
messageTimeout: 3000,
messageColour: '#254137',
messageColourHover: '#7DF799',
messageBorderColour: '#7DF799',
messageDropShadowColour: '#254137',
debug: false
};
configure(configuration: object){
this.config = {...this.config, ...configuration}
}
debug(message: string) {
if (this.config.debug) {
console.log(`CarbonLite: ${message}`)
}
}
init(configuration: CarbonLiteConfig|null = null) {
this.debug('initialising')
if (this.initialised) {
return
}
if (configuration) {
this.configure(configuration)
}
if (this.config.message) {
this.carbonLite.configure(this.config.message);
}
// add styles
const style = document.createElement('style')
style.innerHTML = this.generateStyles()
document.head.appendChild(style)
this.addEventListeners()
this.restartTimer()
this.initialised = true
}
generateStyles() {
return `
carbon-lite {
position: fixed;
bottom: 0;
right: 0;
top: 0;
left: 0;
color: ${this.config.messageColour};
z-index: 2147483646;
background: ${this.config.backgroundColour};
display: flex;
justify-content: center;
align-items: center;
div {
max-width: 80%;
line-height: 1.5rem;
}
}
carbon-lite-message {
position: fixed;
bottom: 100px;
right: 100px;
color: ${this.config.messageColour};
text-align: center;
z-index: 2147483647;
opacity: 0.8;
transition: opacity ${this.config.messageTimeout}ms ease-in;
a {
color: ${this.config.messageColour};
background: ${this.config.backgroundColour};
padding: 20px;
display: block;
text-decoration: none;
border-radius: 20px;
border: 3px solid ${this.config.backgroundColour};
&:hover {
color: ${this.config.messageColourHover};
text-decoration: underline;
border-color: ${this.config.messageBorderColour};
filter: drop-shadow(0 0 0.75rem ${this.config.messageDropShadowColour});
}
svg {
height: 100px;
display: block;
margin: 0 auto 20px auto;
}
}
&.fading {
opacity: 0;
a {
border-color: ${this.config.messageBorderColour};
filter: drop-shadow(0 0 0.75rem ${this.config.messageDropShadowColour});
}
}
&:hover {
transition: opacity 0ms linear;
}
}
`;
}
getIframes() {
let iframes = document.getElementsByTagName(`iframe`);
let iframesArray = [];
for (let i = 0; i < iframes.length; i++) {
iframesArray.push(iframes[i]);
}
return iframesArray;
}
addGlobalEventListener(eventType: string) {
this.debug(`Adding listeners for event type ${eventType}`)
let CarbonLite = this;
let iframes = this.getIframes()
iframes.forEach(iframe => {
iframe.addEventListener(eventType, () => {
CarbonLite.userInteracted()
})
})
document.addEventListener(eventType, () => {
CarbonLite.userInteracted()
})
}
addEventListeners() {
this.debug('adding event listeners')
this.addGlobalEventListener('mousemove')
this.addGlobalEventListener('click')
this.addGlobalEventListener('scroll')
this.addGlobalEventListener('keypress')
this.addGlobalEventListener('resize')
this.addVideoEventListeners()
let CarbonLite = this;
this.debug('adding message event listeners')
CarbonLite.carbonLiteMessage.addEventListener(`mouseenter`, (event) => {
CarbonLite.carbonLiteMessage.classList.remove('fading')
if (CarbonLite.carbonLiteMessageTimer) {
this.debug('clearing message fade out timer due to mouseenter')
clearTimeout(CarbonLite.carbonLiteMessageTimer)
}
})
CarbonLite.carbonLiteMessage.addEventListener(`mouseleave`, (event) => {
CarbonLite.fadeOutMessage()
})
document.addEventListener('carbon-lite-suspend', function(event: CustomEvent) {
CarbonLite.suspend()
});
document.addEventListener('carbon-lite-resume', function(event: CustomEvent) {
CarbonLite.resume()
});
document.addEventListener('carbon-lite-open', (event: CustomEvent) => {
if (event.detail?.interactionDelay) {
this.debug(`ignoring interactions for ${event.detail.interactionDelay} ms`)
CarbonLite.ignoreInteractions = true;
let originalMessage = this.config.message;
if (event.detail.tempMessage) {
this.debug(`Adding a temporary message ${event.detail.tempMessage}`)
this.carbonLite.configure(event.detail.tempMessage);
}
setTimeout(() => {
this.debug('allowing interactions')
CarbonLite.ignoreInteractions = false;
if (originalMessage) {
this.carbonLite.configure(originalMessage);
}
}, event.detail.interactionDelay)
}
CarbonLite.open()
});
}
addVideoEventListeners() {
this.debug('adding video event listeners')
let CarbonLite = this;
let videos = document.getElementsByTagName(`video`);
for (let i = 0; i < videos.length; i++) {
let videoEl = videos[i]
videoEl.addEventListener(`playing`, () => {
CarbonLite.debug('video playing')
CarbonLite.suspend()
})
videoEl.addEventListener(`ended`, () => {
CarbonLite.debug('video ended')
CarbonLite.resume()
})
videoEl.addEventListener(`pause`, () => {
CarbonLite.debug('video paused')
CarbonLite.resume()
})
}
}
restartTimer() {
clearTimeout(this.carbonLiteTimer)
this.carbonLiteTimer = setTimeout(() => { this.open() }, this.config.timeout)
}
userInteracted() {
if (this.ignoreInteractions) {
this.debug('ignoring interactions')
return
}
if (this.backgroundIsVisible()) {
this.debug('user interacted - hiding')
this.hideBackground()
} else {
this.debug('user interacted - restarting timer')
this.restartTimer()
}
}
suspend() {
this.debug('suspending timer')
if (this.backgroundIsVisible()) {
document.body.removeChild(this.carbonLite)
}
if (this.messageIsVisible()) {
document.body.removeChild(this.carbonLiteMessage)
}
clearTimeout(this.carbonLiteTimer)
clearTimeout(this.carbonLiteMessageTimer)
}
backgroundIsVisible() {
return this.carbonLite.parentNode === document.body
}
messageIsVisible() {
return this.carbonLiteMessage.parentNode === document.body
}
resume() {
this.debug('resuming timer')
this.restartTimer()
}
hideBackground() {
if (!this.carbonLite || !this.carbonLite.parentNode) {
return;
}
document.body.removeChild(this.carbonLite)
this.fadeOutMessage()
}
hideMessage() {
let CarbonLite = this;
if (!CarbonLite.carbonLiteMessage || !CarbonLite.carbonLiteMessage.parentNode) {
return;
}
this.debug('hiding message')
document.body.removeChild(CarbonLite.carbonLiteMessage)
this.debug('clearing message fade out timer due to message being hidden')
clearTimeout(CarbonLite.carbonLiteMessageTimer)
CarbonLite.carbonLiteMessage.classList.remove('fading')
this.debug('restarting timer after message has been hidden')
CarbonLite.restartTimer()
}
fadeOutMessage() {
this.debug('setting timer to fade out message')
this.carbonLiteMessage.classList.add('fading')
this.carbonLiteMessageTimer = setTimeout(() => { this.hideMessage() }, this.config.messageTimeout)
}
open() {
this.debug('opening')
let CarbonLite = this;
if (CarbonLite.carbonLiteMessageTimer) {
this.debug('clearing message fade out timer')
clearTimeout(CarbonLite.carbonLiteMessageTimer)
}
document.body.appendChild(CarbonLite.carbonLite)
document.body.appendChild(CarbonLite.carbonLiteMessage)
}
}