Compare commits
2 commits
main
...
feat/anima
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d86adf6f3 | |||
| 3a4a450fbb |
3 changed files with 218 additions and 2 deletions
209
public/components/sparkly-text.js
Normal file
209
public/components/sparkly-text.js
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
/**
|
||||||
|
* Thanks to sparkly-text web component by Stefan Judis: https://github.com/stefanjudis/sparkly-text
|
||||||
|
*/
|
||||||
|
|
||||||
|
let sheet
|
||||||
|
let sparkleTemplate
|
||||||
|
|
||||||
|
// https://caniuse.com/mdn-api_cssstylesheet_replacesync
|
||||||
|
const supportsConstructableStylesheets =
|
||||||
|
'replaceSync' in CSSStyleSheet.prototype
|
||||||
|
|
||||||
|
const motionOK = window.matchMedia('(prefers-reduced-motion: no-preference)')
|
||||||
|
|
||||||
|
class SparklyText extends HTMLElement {
|
||||||
|
#numberOfSparkles = 3
|
||||||
|
#sparkleSvg = `<svg width="1200" height="1200" viewBox="0 0 1200 1200" aria-hidden="true">
|
||||||
|
<path fill="red" d="m611.04 866.16c17.418-61.09 50.25-116.68 95.352-161.42 45.098-44.742 100.94-77.133 162.17-94.062l38.641-10.68-38.641-10.68c-61.227-16.93-117.07-49.32-162.17-94.062-45.102-44.738-77.934-100.33-95.352-161.42l-11.039-38.641-11.039 38.641c-17.418 61.09-50.25 116.68-95.352 161.42-45.098 44.742-100.94 77.133-162.17 94.062l-38.641 10.68 38.641 10.68c61.227 16.93 117.07 49.32 162.17 94.062 45.102 44.738 77.934 100.33 95.352 161.42l11.039 38.641z"/>
|
||||||
|
</svg>`
|
||||||
|
|
||||||
|
#css = `
|
||||||
|
:host {
|
||||||
|
--_sparkle-base-size: var(--sparkly-text-size, 1em);
|
||||||
|
--_sparkle-base-animation-length: var(--sparkly-text-animation-length, 1.5s);
|
||||||
|
--_sparkle-base-color: var(--sparkly-text-color, #4ab9f8);
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
width: var(--_sparkle-base-size);
|
||||||
|
height: var(--_sparkle-base-size);
|
||||||
|
transform-origin: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
svg {
|
||||||
|
animation: sparkle-spin var(--_sparkle-base-animation-length) linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.rainbow path {
|
||||||
|
animation: rainbow-colors calc(var(--_sparkle-base-animation-length) * 2) linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg path {
|
||||||
|
fill: var(--_sparkle-base-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rainbow-colors {
|
||||||
|
0%, 100% { fill: #ff0000; }
|
||||||
|
14% { fill: #ff8000; }
|
||||||
|
28% { fill: #ffff00; }
|
||||||
|
42% { fill: #00ff00; }
|
||||||
|
56% { fill: #0000ff; }
|
||||||
|
70% { fill: #4b0082; }
|
||||||
|
84% { fill: #8f00ff; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sparkle-spin {
|
||||||
|
0% {
|
||||||
|
scale: 0;
|
||||||
|
opacity: 0;
|
||||||
|
rotate: 0deg;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
scale: 1;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
scale: 0;
|
||||||
|
opacity: 0;
|
||||||
|
rotate: 180deg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
static register() {
|
||||||
|
if ('customElements' in window) {
|
||||||
|
window.customElements.define('sparkly-text', SparklyText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateCss() {
|
||||||
|
if (!sheet) {
|
||||||
|
if (supportsConstructableStylesheets) {
|
||||||
|
sheet = new CSSStyleSheet()
|
||||||
|
sheet.replaceSync(this.#css)
|
||||||
|
} else {
|
||||||
|
sheet = document.createElement('style')
|
||||||
|
sheet.textContent = this.#css
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportsConstructableStylesheets) {
|
||||||
|
this.shadowRoot.adoptedStyleSheets = [sheet]
|
||||||
|
} else {
|
||||||
|
this.shadowRoot.append(sheet.cloneNode(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
const needsSparkles = motionOK.matches || !this.shadowRoot
|
||||||
|
|
||||||
|
if (!this.shadowRoot) {
|
||||||
|
this.attachShadow({ mode: 'open' })
|
||||||
|
this.generateCss()
|
||||||
|
this.shadowRoot.append(document.createElement('slot'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsSparkles) {
|
||||||
|
this.#numberOfSparkles = parseInt(
|
||||||
|
this.getAttribute('number-of-sparkles') || `${this.#numberOfSparkles}`,
|
||||||
|
10
|
||||||
|
)
|
||||||
|
|
||||||
|
if (Number.isNaN(this.#numberOfSparkles)) {
|
||||||
|
throw new Error(`Invalid number-of-sparkles value`)
|
||||||
|
}
|
||||||
|
this.cleanupSparkles()
|
||||||
|
this.addSparkles()
|
||||||
|
}
|
||||||
|
|
||||||
|
motionOK.addEventListener('change', this.motionOkChange)
|
||||||
|
window.addEventListener('popstate', this.handleNavigation)
|
||||||
|
window.addEventListener('pageshow', this.handlePageShow)
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
motionOK.removeEventListener('change', this.motionOkChange)
|
||||||
|
window.removeEventListener('popstate', this.handleNavigation)
|
||||||
|
window.removeEventListener('pageshow', this.handlePageShow)
|
||||||
|
this.cleanupSparkles()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNavigation = () => {
|
||||||
|
if (motionOK.matches) {
|
||||||
|
this.cleanupSparkles()
|
||||||
|
this.addSparkles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePageShow = (event) => {
|
||||||
|
// If the page is being loaded from the bfcache
|
||||||
|
if (event.persisted && motionOK.matches) {
|
||||||
|
this.cleanupSparkles()
|
||||||
|
this.addSparkles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupSparkles() {
|
||||||
|
// Remove all existing sparkle SVGs
|
||||||
|
const sparkles = this.shadowRoot.querySelectorAll('svg')
|
||||||
|
sparkles.forEach((sparkle) => sparkle.remove())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare as an arrow function to get the appropriate 'this'
|
||||||
|
motionOkChange = () => {
|
||||||
|
if (motionOK.matches) {
|
||||||
|
this.addSparkles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addSparkles() {
|
||||||
|
for (let i = 0; i < this.#numberOfSparkles; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.addSparkle((sparkle) => {
|
||||||
|
sparkle.style.top = `calc(${
|
||||||
|
Math.random() * 110 - 5
|
||||||
|
}% - var(--_sparkle-base-size) / 2)`
|
||||||
|
sparkle.style.left = `calc(${
|
||||||
|
Math.random() * 110 - 5
|
||||||
|
}% - var(--_sparkle-base-size) / 2)`
|
||||||
|
})
|
||||||
|
}, i * 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addSparkle(update) {
|
||||||
|
if (!sparkleTemplate) {
|
||||||
|
const span = document.createElement('span')
|
||||||
|
span.innerHTML = this.#sparkleSvg
|
||||||
|
sparkleTemplate = span.firstElementChild.cloneNode(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const sparkleWrapper = sparkleTemplate.cloneNode(true)
|
||||||
|
|
||||||
|
// Add rainbow class if --sparkly-text-color is set to 'rainbow'
|
||||||
|
const styles = getComputedStyle(this)
|
||||||
|
if (styles.getPropertyValue('--sparkly-text-color').trim() === 'rainbow') {
|
||||||
|
sparkleWrapper.classList.add('rainbow')
|
||||||
|
}
|
||||||
|
|
||||||
|
update(sparkleWrapper)
|
||||||
|
this.shadowRoot.appendChild(sparkleWrapper)
|
||||||
|
sparkleWrapper.addEventListener('animationiteration', () => {
|
||||||
|
update(sparkleWrapper)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SparklyText.register()
|
||||||
|
|
||||||
|
export { SparklyText }
|
||||||
|
|
@ -54,4 +54,7 @@ const baseURL = Astro.site?.toString().slice(0, -1) // ?? 'https://ayo.ayco.io'
|
||||||
<link rel="icon" href="favicon.svg" />
|
<link rel="icon" href="favicon.svg" />
|
||||||
<link rel="mask-icon" href="mask-icon.svg" color="#000000" />
|
<link rel="mask-icon" href="mask-icon.svg" color="#000000" />
|
||||||
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
|
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
|
||||||
|
|
||||||
|
<!-- web components -->
|
||||||
|
<script type="module" src="components/sparkly-text.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,8 @@ const avatarSize = 150
|
||||||
<main>
|
<main>
|
||||||
<section class="introduction-section">
|
<section class="introduction-section">
|
||||||
<p>
|
<p>
|
||||||
I care about the <em>Web</em>, and I love to <em>create</em> stuff to <em
|
I care about the <em>Web</em>, and I love to <em>create</em> stuff to <sparkly-text
|
||||||
>inspire</em
|
><em>inspire</em></sparkly-text
|
||||||
> and <em>serve</em> others.
|
> and <em>serve</em> others.
|
||||||
<!--
|
<!--
|
||||||
<a href="/about">More?</a>
|
<a href="/about">More?</a>
|
||||||
|
|
@ -245,4 +245,8 @@ const avatarSize = 150
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sparkly-text {
|
||||||
|
--sparkly-text-color: orange;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue