|
const ARTIFICIAL_DELAY = 2000; |
|
|
|
function createElementFromHTML(htmlString) { |
|
const div = document.createElement('div'); |
|
div.innerHTML = htmlString; |
|
return div.firstChild; |
|
} |
|
|
|
class CloudinaryImg extends HTMLElement { |
|
static get observedAttributes() { |
|
return ['src', 'animatedStroke']; |
|
} |
|
constructor() { |
|
super(); |
|
this.lowResWidth = 150; |
|
this.cloudName = this.getAttribute('cloudinary'); |
|
this.attachShadow({mode: 'open'}); |
|
this.initialise(); |
|
this.render(); |
|
} |
|
|
|
initialise() { |
|
this.imageLoaded = false; |
|
this.svg = undefined; |
|
this.svgElement = undefined; |
|
this.width = this.getAttribute('width') || this.lowResWidth; |
|
this.height = this.getAttribute('height') || this.lowResWidth; |
|
|
|
this.shadowRoot.innerHTML = ` |
|
<style> |
|
:host { |
|
width: ${this.width}px; |
|
} |
|
.background { |
|
position: absolute; |
|
z-index: -1; |
|
} |
|
.fade-in { |
|
opacity: 1; |
|
animation-name: fadeInOpacity; |
|
animation-iteration-count: 1; |
|
animation-timing-function: ease-in; |
|
animation-duration: 0.2s; |
|
} |
|
.animate-dash { |
|
stroke-dasharray: var(--path-length); |
|
stroke-dashoffset: var(--path-length); |
|
animation: dash 5s linear forwards; |
|
} |
|
|
|
@keyframes fadeInOpacity { |
|
0% { |
|
opacity: 0; |
|
} |
|
100% { |
|
opacity: 1; |
|
} |
|
} |
|
|
|
@keyframes dash { |
|
to { |
|
stroke-dashoffset: 0; |
|
} |
|
} |
|
</style> |
|
`; |
|
} |
|
|
|
render() { |
|
this.traceImage( |
|
`https://res.cloudinary.com/${this.cloudName}/image/fetch/q_10,f_jpg,w_${this.lowResWidth}/${this.getAttribute('src')}`, |
|
(svg) => { |
|
this.svg = svg; |
|
this.showSVGWhenConnected(); |
|
} |
|
); |
|
|
|
this.loadFullImage(() => { |
|
this.imageLoaded = true; |
|
this.showFullImageWhenLoaded(); |
|
}); |
|
} |
|
|
|
// Only called for the disabled and open attributes due to observedAttributes |
|
attributeChangedCallback(name, oldValue, newValue) { |
|
if (name === 'src' && oldValue !== newValue) { |
|
this.initialise(); |
|
this.render(); |
|
} |
|
} |
|
|
|
traceImage(url, callback) { |
|
// Enable CORS support |
|
window.Potrace.img.crossOrigin = 'Anonymous'; |
|
window.Potrace.loadImageFromUrl(url); |
|
|
|
window.Potrace.process(() => { |
|
callback(window.Potrace.getSVG(this.width / this.lowResWidth, "curve")); |
|
}); |
|
} |
|
|
|
loadFullImage(done) { |
|
this.fullImage = createElementFromHTML(`<img alt="${this.getAttribute('alt') || ''}" width=${this.width} />`) |
|
this.fullImage.onload = () => { |
|
done(); |
|
}; |
|
|
|
// artificial delay for fast networks |
|
window.setTimeout(() => { |
|
this.fullImage.src = this.getAttribute('src'); |
|
}, ARTIFICIAL_DELAY); |
|
} |
|
|
|
showSVGWhenConnected() { |
|
// Only proceed if we're mounted in the DOM, the svg is loaded, and we haven't already shown the image |
|
if (!this.connected || !this.svg || this.imageLoaded) { |
|
return; |
|
} |
|
this.svgElement = createElementFromHTML(this.svg); |
|
this.svgElement.classList.add('fade-in'); |
|
if (this.getAttribute('animatedStroke')) { |
|
const pathEl = this.svgElement.querySelector('path'); |
|
pathEl.setAttribute('stroke-width', this.getAttribute('stroke-width') || '3'); |
|
pathEl.setAttribute('stroke', this.getAttribute('color') || 'gray'); |
|
pathEl.classList.add('animate-dash'); |
|
const pathLength = Math.ceil(pathEl.getTotalLength()); |
|
pathEl.style.setProperty('--path-length', pathLength); |
|
} else { |
|
pathEl.setAttribute('fill', this.getAttribute('color') || 'gray'); |
|
} |
|
this.shadowRoot.appendChild(this.svgElement); |
|
} |
|
|
|
showFullImageWhenLoaded() { |
|
// Only proceed if we're mounted in the DOM, and the image is loaded |
|
if (!this.connected || !this.imageLoaded) { |
|
return; |
|
} |
|
if (this.svgElement) { |
|
this.svgElement.classList.add('background'); |
|
} |
|
this.fullImage.classList.add('fade-in'); |
|
this.shadowRoot.appendChild(this.fullImage); |
|
} |
|
|
|
connectedCallback() { |
|
this.connected = true; |
|
this.showSVGWhenConnected(); |
|
this.showFullImageWhenLoaded(); |
|
} |
|
} |
|
|
|
customElements.define('cloudinary-svg-placeholder', CloudinaryImg); |
|
|
|
const imgEL = document.querySelector('cloudinary-svg-placeholder'); |
|
|
|
document.querySelectorAll('a.alternateImg').forEach((node) => { |
|
node.addEventListener('click', (event) => { |
|
event.preventDefault(); |
|
event.stopPropagation(); |
|
imgEL.setAttribute('src', event.target.getAttribute('href')); |
|
}) |
|
}) |