<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<!------ Include the above in your HEAD tag ---------->
<!DOCTYPE html><html lang='en' class=''>
<head><script src='//production-assets.codepen.io/assets/editor/live/console_runner-079c09a0e3b9ff743e39ee2d5637b9216b3545af0de366d4b9aad9dc87e26bfd.js'></script><script src='//production-assets.codepen.io/assets/editor/live/events_runner-73716630c22bbc8cff4bd0f07b135f00a0bdc5d14629260c3ec49e5606f98fdd.js'></script><script src='//production-assets.codepen.io/assets/editor/live/css_live_reload_init-2c0dc5167d60a5af3ee189d570b1835129687ea2a61bee3513dee3a50c115a77.js'></script><meta charset='UTF-8'><meta name="robots" content="noindex"><link rel="shortcut icon" type="image/x-icon" href="//production-assets.codepen.io/assets/favicon/favicon-8ea04875e70c4b0bb41da869e81236e54394d63638a1ef12fa558a4a835f1164.ico" /><link rel="mask-icon" type="" href="//production-assets.codepen.io/assets/favicon/logo-pin-f2d2b6d2c61838f7e76325261b7195c27224080bc099486ddd6dccb469b8e8e6.svg" color="#111" /><link rel="canonical" href="https://codepen.io/milesmanners/pen/MEwzMz" />
<style class="cp-pen-styles">@import url("https://fonts.googleapis.com/css?family=Open+Sans");
*, *::before, *::after {
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
}
body {
background: #1C1F2B;
color: #fff;
margin: 0px;
font-family: 'Open Sans', sans-serif;
}
.container {
display: flex;
height: 100%;
flex-direction: column;
justify-content: center;
padding: 20px;
}
.logo {
position: relative;
margin: auto;
}
.logo .mask, .logo canvas {
display: block;
width: inherit;
height: inherit;
}
.logo .mask {
position: absolute;
top: -1px;
left: -1px;
text-indent: -9999px;
box-sizing: content-box;
}
</style></head><body>
<div class="container">
<div class="logo"></div>
</div>
<script src='//production-assets.codepen.io/assets/common/stopExecutionOnTimeout-b2a7b3fe212eaa732349046d8416e00a9dec26eb7fd347590fbced3ab38af52e.js'></script>
<script>/* jshint esversion: 6, asi: true, boss: true */
let $mm = Object.assign((s, attrs) => {
if (s === null)
return null
let el
if (s instanceof Element || s instanceof HTMLDocument || s instanceof Window) {
el = s
} else {
let match = s.match(/<\/?(.*?)>/)
el = match ? $mm.create(match[1], attrs) : $mm.find(s)
}
if (el === null)
return null
$mm.merge(el, $mm.fn)
Object.defineProperty(el, 'val', {
configurable: true,
get: () => el.value,
set: v => el.value = v
})
Object.defineProperty(el, 'parent', {
configurable: true,
get: () => $mm(el.parentElement)
})
Object.defineProperty(el, 'next', {
configurable: true,
get: () => $mm(el.nextElementSibling),
set: v => el.appendAfter(v)
})
Object.defineProperty(el, 'prev', {
configurable: true,
get: () => $mm(el.previousElementSibling),
set: v => el.appendBefore(v)
})
Object.defineProperty(el, 'bounds', {
configurable: true,
get: () => el.getBoundingClientRect()
})
Object.defineProperty(el, 'text', {
configurable: true,
get: () => el.textContent,
set: v => el.textContent = v
})
Object.defineProperty(el, 'html', {
configurable: true,
get: () => el.innerHTML,
set: v => el.innerHTML = v
})
return el
}, {
find: s => $mm(document.querySelector(s)),
findAll: s => [...document.querySelectorAll(s)].map(e => $mm(e)),
create: (elName, attrs) => {
let el = document.createElement(elName)
if (attrs !== undefined) {
let keys = Object.keys(attrs),
styleKey = keys.find(k => k.toLowerCase() === 'style' && $mm.isObj(attrs[k])),
innerHtmlKey = keys.find(k => k.toLowerCase() === 'innerhtml' || k.toLowerCase() === 'html')
if (styleKey !== undefined) {
for (let k in attrs[styleKey])
el.style[k] = attrs[styleKey][k]
delete attrs[styleKey]
}
if (innerHtmlKey !== undefined) {
el.innerHTML = attrs[innerHtmlKey]
delete attrs[innerHtmlKey]
}
for (let k in attrs)
el.setAttribute(k, attrs[k])
}
return $mm(el)
},
createSvg: (elName, attrs) => {
let el = document.createElementNS('http://www.w3.org/2000/svg', elName)
if (attrs !== undefined) {
let keys = Object.keys(attrs),
styleKey = keys.find(k => k.toLowerCase() === 'style' && $mm.isObj(attrs[k])),
innerHtmlKey = keys.find(k => k.toLowerCase() === 'innerhtml' || k.toLowerCase() === 'html')
if (styleKey !== undefined) {
for (let k in attrs[styleKey])
el.style[k] = attrs[styleKey][k]
delete attrs[styleKey]
}
if (innerHtmlKey !== undefined) {
el.innerHTML = attrs[innerHtmlKey]
delete attrs[innerHtmlKey]
}
for (let k in attrs)
el.setAttribute(k, attrs[k])
}
return $mm(el)
},
rand: (max = 1, min = 0) => Math.random()*(max-min)+min,
randInt: (max, min = 0) => ~~(Math.random()*(max-min)+min),
randArr: arr => arr[$mm.randInt(arr.length)],
isObj: obj => typeof obj === 'object' && obj.constructor !== RegExp && obj.constructor !== Date,
merge: (deep, ...objs) => {
if (deep !== true)
return Object.assign(deep, ...objs)
return objs.reduce((dest, src) => {
if (Array.isArray(src))
return Array.isArray(dest) ? dest.concat(src) : src
Object.keys(src).forEach(k => {
if (src[k] && $mm.isObj(src[k]))
$mm.merge(true, dest[k], src[k])
else
dest[k] = src[k]
})
return dest
})
},
extend: (...objs) => {
return $mm.merge($mm, ...objs)
},
round: (num, prec = 2) => {
let x = 10**prec
return Math.round(num*x)/x
},
dist: (p1, p2) => Math.hypot(p1.x - p2.x, p1.y - p2.y),
cssVar: (v, val) => {
if (val === undefined)
return window.getComputedStyle(document.body).getPropertyValue(`--${v}`).trim()
document.documentElement.style.setProperty(`--${v}`, val)
},
array: (n, fn) => Array.from({length: n}, fn),
fn: {
find: function (s) { return $mm(this.querySelector(s)) },
findAll: function (s) { return [...this.querySelectorAll(s)].map(e => $mm(e)) },
on: function (type, ...args) {
for (let t of type.split(' '))
this.addEventListener(t, ...args)
return this
},
appendBefore: function (el) {
this.parent.insertBefore(el, this)
return this
},
appendAfter: function (el) {
this.parent.insertBefore(el, this.nextSibling)
return this
},
appendTo: function (el) {
el.append(this)
return this
},
addClass: function (c) {
this.classList.add(c)
return this
},
removeClass: function (c) {
this.classList.remove(c)
return this
},
toggleClass: function (c) {
this.classList.toggle(c)
return this
},
hasClass: function (c) {
return this.classList.contains(c)
},
attr: function (a, val) {
if (val === undefined)
return this.getAttribute(a)
this.setAttribute(a, val)
return this
},
data: function (a, val) {
if (val === undefined)
return this.getAttribute(`data-${a}`)
this.setAttribute(`data-${a}`, val)
return this
},
getStyle: function (...attrs) {
let style = window.getComputedStyle(this)
let ret = {}
if (attrs.length > 1) {
for (let a of attrs)
ret[a] = style.getPropertyValue(a)
} else if (attrs.length == 1) {
ret = style.getPropertyValue(attrs[0])
} else {
for (let a in style)
ret[a] = style.getPropertyValue(a)
}
return ret
},
serialize: function () {
let obj = {}
this.findAll('input, select, textarea').forEach(field => {
switch (field.type) {
case 'checkbox':
obj[field.name] = field.checked
break
case 'radio':
if (field.checked)
obj[field.name] = field.value
else if (!(field.name in obj))
obj[field.name] = null
break
default:
obj[field.name] = field.value
}
})
return obj
},
validate: function () {
let fields = this.findAll('[data-required]'),
obj = {}
fields.forEach(field => {
switch (field.type) {
case 'checkbox':
break
case 'radio':
if (field.checked)
obj[field.name] = true
else if (!(field.name in obj))
obj[field.name] = false
break
default:
obj[field.name] = field.value.length && field.validity.valid
}
})
let valid = Object.values(obj).every(e => e)
if (!valid)
fields.forEach(field => field.attr('required', ''))
return $mm.merge({valid: valid}, obj)
},
extend: (...objs) => {
return $mm.merge($mm.fn, ...objs)
}
}
})</script><script src='https://cdnjs.cloudflare.com/ajax/libs/granim/1.0.6/granim.min.js'></script><script src='https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.5/dat.gui.min.js'></script><script src='https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js'></script>
<script >/* jshint esversion: 6, asi: true, boss: true */
let loadedFonts = ['Serif']
let fonts = ['Acme', 'Anton', 'Arvo', 'Berkshire Swash', 'Dancing Script', 'Great Vibes', 'Indie Flower', 'Julius Sans One', 'Lobster', 'Open Sans', 'Oswald', 'Pacifico', 'Princess Sofia', 'Rock Salt', 'Serif', 'VT323', 'Yellowtail']
let opts = {
fontSize: 128,
font: $mm.randArr(fonts),
text: 'Granim.js',
bg: '#1C1F2B'
}
let el = $mm('.logo')
let canvasEl = $mm('<canvas>').appendTo(el)
let maskEl = $mm('<a>', {
class: 'mask',
href: 'javascript:window.location.href=window.location.href',
html: opts.text,
style: {
border: `2px solid ${opts.bg}`
}
}).appendTo(el)
CanvasRenderingContext2D.prototype.getTextHeight = function (text) {
let body = $mm('body')
let dummy = $mm('<div>', { style: { font: this.font } })
dummy.append(document.createTextNode(text))
body.append(dummy)
let result = dummy.offsetHeight
body.removeChild(dummy)
return result
}
// Creates a mask canvas for the given parameters
function textMask (text, fontSize, font, bg) {
const canvas = $mm('<canvas>')
const ctx = canvas.getContext('2d')
// Looks complicated, but boils down to checking if the font is loaded
// and loading it if it's not before creating the mask image
return new Promise((resolve, reject) => {
function createMask () {
ctx.font = `${fontSize}px '${font}'`
const metrics = ctx.measureText(text)
const textWidth = metrics.width
const textHeight = ctx.getTextHeight(text)
canvas.attr('width', ~~(textWidth + textWidth / 10))
canvas.attr('height', ~~(textHeight + textHeight / 10))
ctx.textBaseline = 'hanging'
ctx.font = `${fontSize}px ${font}`
ctx.fillStyle = bg
ctx.fillText(text, textWidth / 20, textHeight / 6)
ctx.globalCompositeOperation = 'xor'
ctx.fillRect(0, 0, canvas.width, canvas.height)
resolve(canvas)
}
if (loadedFonts.includes(font)) {
createMask()
} else {
WebFont.load({
google: {
families: [font]
},
active: () => {
loadedFonts.push(font)
createMask()
}
})
}
})
}
// Update the example
function updateMask () {
// Create the mask
textMask(opts.text, opts.fontSize, opts.font, opts.bg).then(mask => {
// Resize the container to fit the mask
el.style.width = `${mask.attr('width')}px`
el.style.height = `${mask.attr('height')}px`
// Use the canvas as the background image
maskEl.style.backgroundImage = `url(${mask.toDataURL()})`
})
}
// Run the example
updateMask()
// Initialize Granim
new Granim({
element: canvasEl,
direction: 'left-right',
opacity: [1, 1],
states : {
'default-state': {
gradients: [
['#EB3349', '#F45C43'],
['#FF8008', '#FFC837'],
['#4CB8C4', '#3CD3AD'],
['#24C6DC', '#514A9D'],
['#FF512F', '#DD2476'],
['#DA22FF', '#9733EE']
],
transitionSpeed: 2000
}
}
})
// Initialize Dat GUI
let gui = new dat.GUI()
gui.add(opts, 'text').onChange(updateMask)
gui.add(opts, 'font', fonts).onChange(updateMask)
gui.add(opts, 'fontSize', 12, 400).step(1).onChange(updateMask)
//# sourceURL=pen.js
</script>
</body></html>