Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save neoOpus/6adeede7882df288c99aba5c9d9bd1f3 to your computer and use it in GitHub Desktop.
Save neoOpus/6adeede7882df288c99aba5c9d9bd1f3 to your computer and use it in GitHub Desktop.
CSS Ring Text with CSS Trigonometric Functions
<section>
<h1 class="ring"></h1>
</section>
import { GUI } from 'https://cdn.skypack.dev/dat.gui'
const canTrig = CSS.supports('(top: calc(sin(1) * 1px))')
const HEADING = document.querySelector('h1')
const CTRL = new GUI()
const OPTIONS = {
SPACING: 1,
SIZE: 1,
TEXT: 'Made by Jhey with CSS Trig functions • '
}
const genCode = () => `
.ring {
--char-count: ${HEADING.children.length};
--inner-angle: calc((360 / var(--char-count, ${HEADING.children.length})) * 1deg);
--character-width: ${OPTIONS.SPACING.toFixed(1)};
--radius: calc((var(--character-width, ${OPTIONS.SPACING.toFixed(1)}) / ${canTrig ? 'sin(var(--inner-angle))' : Math.sin(
360 / HEADING.children.length / (180 / Math.PI)
)}) * -1ch;
--font-size: ${OPTIONS.SIZE.toFixed(1)}rem;
font-family: monospace;
text-transform: uppercase;
font-size: calc(var(--font-size, 1) * 1rem);
animation: rotation 6s infinite linear;
position: relative;
}
.char {
display: inline-block;
position: absolute;
top: 50%;
left: 50%;
transform:
translate(-50%, -50%)
rotate(calc(var(--inner-angle) * var(--char-index)))
translateY(var(--radius));
}
@keyframes rotation {
to {
rotate: -360deg;
}
}
`.trim()
const CSS_CODE = Object.assign(document.createElement('li'), {
className: 'code-block',
innerHTML: `<pre><code>${genCode()}</code></pre>`
})
const HTML_CODE = Object.assign(document.createElement('li'), {
className: 'code-block',
innerHTML: `<pre><code>${HEADING.outerHTML}</code></pre>`
})
const onUpdate = () => {
// Make the ring text
const text = OPTIONS.TEXT
// 1. Take the text and split it into spans...
const chars = text.split('')
HEADING.innerHTML = ''
HEADING.style.setProperty('--char-count', chars.length)
for (let c = 0; c < chars.length; c++) {
HEADING.innerHTML += `<span aria-hidden="true" class="char" style="--char-index: ${c};">${chars[c]}</span>`
}
HEADING.innerHTML += `<span class="sr-only">${OPTIONS.TEXT}</span>`
// Set the styles
HEADING.style.setProperty('--font-size', OPTIONS.SIZE)
HEADING.style.setProperty('--character-width', OPTIONS.SPACING)
HEADING.style.setProperty(
'--radius',
canTrig
? 'calc((var(--character-width) / sin(var(--inner-angle))) * -1ch'
: `calc(
(${OPTIONS.SPACING} / ${Math.sin(
360 / HEADING.children.length / (180 / Math.PI)
)})
* -1ch
)`
)
if (HEADING.children.length > 3) {
document.documentElement.style.setProperty(
'--buffer',
canTrig
? `calc((${OPTIONS.SPACING} / sin(${
360 / HEADING.children.length
}deg)) * ${OPTIONS.SIZE}rem)`
: `calc((${OPTIONS.SPACING} / ${Math.sin(
360 / HEADING.children.length / (180 / Math.PI)
)}) * ${OPTIONS.SIZE}rem)`
)
}
CSS_CODE.innerHTML = `<pre><code>${genCode()}</code></pre>`
// HTML_CODE.innerText = `<pre><code>${HEADING.outerHTML}</code></pre>`
}
CTRL.add(OPTIONS, 'SPACING', 0.5, 2, 0.1).name('Spacing (ch)').onChange(onUpdate)
CTRL.add(OPTIONS, 'SIZE', 0.25, 2, 0.1).name('Size (rem)').onChange(onUpdate)
CTRL.add(OPTIONS, 'TEXT').name('Text').onChange(onUpdate)
// const MARKUP = CTRL.addFolder('HTML')
const STYLES = CTRL.addFolder('CSS')
STYLES.domElement.querySelector('ul').appendChild(CSS_CODE)
// MARKUP.domElement.querySelector('ul').appendChild(HTML_CODE)
// const CODE = document.querySelector('#code')
onUpdate()
<script src="https://codepen.io/jh3y/pen/zYRjgjW.js"></script>
@import url('https://unpkg.com/open-props/normalize.min.css');
@import url('https://unpkg.com/open-props/open-props.min.css');
*,
*:after,
*:before {
box-sizing: border-box;
}
body {
/* display: grid;
place-items: center;
align-content: center;
overflow: hidden; */
display: grid;
place-items: center;
min-height: 100vh;
font-family: 'Google Sans', sans-serif, system-ui;
accent-color: var(--red-6);
}
pre {
padding: var(--size-2);
overflow: auto;
}
ul:not(.closed) > .code-block {
height: auto !important;
line-height: 1 !important;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
*:focus-visible {
outline-color: var(--red-6);
}
input::selection {
background: var(--red-6);
color: var(--text-1);
background: hsl(0 100% 50%);
color: hsl(0 0% 100%);
}
/*
* To get the radius or the hypoteneuse.
* If you know the inner angle and the length of the side
* The end / Math.sin(innerAngle) should provide the radius
*/
section:first-of-type {
min-height: 250px;
height: calc(2 * var(--buffer));
aspect-ratio: 1;
display: grid;
place-items: center;
}
section:last-of-type {
display: grid;
gap: 2rem;
}
form {
display: inline-grid;
grid-template-columns: auto auto;
gap: 0.5rem 1rem;
justify-content: center;
}
:where(p,ul,ol,dl,h6) {
font-size: var(--font-size-0);
}
.ring {
/* --character-width: 1ch; */
--inner-angle: calc((360 / var(--char-count)) * 1deg);
--character-width: 1;
font-family: monospace;
text-transform: uppercase;
font-size: calc(var(--font-size, 1) * 1rem);
animation: rotation 6s infinite linear;
position: relative;
}
@keyframes rotation {
to {
rotate: -360deg;
}
}
.char {
display: inline-block;
position: absolute;
top: 50%;
left: 50%;
/* line-height: 1; */
transform:
translate(-50%, -50%)
rotate(calc(var(--inner-angle) * var(--char-index)))
translateY(var(--radius));
}
<link href="https://codepen.io/jh3y/pen/zYRjgjW.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment