Author: | Aaron Ike |
---|---|
Views Total: | 241 views |
Official Page: | Go to website |
Last Update: | November 6, 2020 |
License: | MIT |
Preview:

Description:
A pretty cool URL input field that automatically updates the favicon based on the site URL you provide.
See It In Action:
See the Pen
URL Favicon Input by Aaron Iker (@aaroniker)
on CodePen.
How to use it:
1. Code the HTML for the URL input.
<div class="url-input"> <div class="icon"> <svg viewBox="0 0 18 18"> <path d="M10.05 7.95001C11.55 9.45001 11.55 11.775 10.05 13.275L7.95 15.375C6.45 16.875 4.125 16.875 2.625 15.375C1.125 13.875 1.125 11.55 2.625 10.05L4.5 8.25001" /> <path d="M7.9502 10.05C6.4502 8.55 6.4502 6.225 7.9502 4.725L10.0502 2.625C11.5502 1.125 13.8752 1.125 15.3752 2.625C16.8752 4.125 16.8752 6.45 15.3752 7.95L13.5002 9.75" /> </svg> <div class="favicon"></div> </div> <div class="text"> <input type="text" placeholder="Your URL" /> </div> <button class="clear"> <svg viewBox="0 0 24 24"> <path class="line" d="M2 2L22 22" /> <path class="long" d="M9 15L20 4" /> <path class="arrow" d="M13 11V7" /> <path class="arrow" d="M17 11H13" /> </svg> </button> </div>
2. The core CSS for the URL input.
.url-input { --background: #fff; --border-default: #E1E6F9; --border-active: #275EFE; --text-color: #646B8C; --placeholder-color: #BBC1E1; --icon: #275EFE; --close: #646B8C; --close-light: #A6ACCD; --close-background: #EFF2FB; width: 100%; max-width: 240px; display: -webkit-box; display: flex; -webkit-box-align: center; align-items: center; position: relative; border-radius: 8px; background: var(--background); box-shadow: inset 0 0 0 var(--border-width, 1px) var(--border, var(--border-default)); -webkit-transition: box-shadow .2s; transition: box-shadow .2s; --favicon-scale: 0; --icon-offset: 0px; --clear-x: 0px; --clear-swipe-left: 0px; --clear-swipe-x: 0; --clear-swipe: 0px; --clear-scale: 0; --clear-rotate: 0deg; --clear-opacity: 0; --clear-arrow-o: 1; --clear-arrow-x: 0px; --clear-arrow-y: 0px; --clear-arrow-offset: 4px; --clear-arrow-offset-second: 4px; --clear-line-array: 8.5px; --clear-line-offset: 27px; --clear-long-array: 8.5px; --clear-long-offset: 24px; } .url-input .icon { position: absolute; left: 15px; top: 15px; pointer-events: none; } .url-input .icon svg, .url-input .icon img { display: block; width: 18px; height: 18px; } .url-input .icon svg { fill: none; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; stroke: var(--icon); } .url-input .icon svg path { stroke-dasharray: 24px; stroke-dashoffset: var(--icon-offset); } .url-input .icon .favicon { position: absolute; left: 0; top: 0; -webkit-transform: scale(var(--favicon-scale)) translateZ(0); transform: scale(var(--favicon-scale)) translateZ(0); } .url-input .text { -webkit-box-flex: 1; flex-grow: 1; } .url-input .text input { -webkit-appearance: none; line-height: 24px; background: none; border: none; outline: none; display: block; width: 100%; margin: 0; padding: 12px 12px 12px 44px; font-family: inherit; font-size: 14px; font-weight: 500; color: var(--text-color); } .url-input .text input::-webkit-input-placeholder { color: var(--placeholder-color); } .url-input .text input::-moz-placeholder { color: var(--placeholder-color); } .url-input .text input:-ms-input-placeholder { color: var(--placeholder-color); } .url-input .text input::-ms-input-placeholder { color: var(--placeholder-color); } .url-input .text input::placeholder { color: var(--placeholder-color); }
3. The CSS styles for the clearing animation.
.url-input.clearing, .url-input:focus-within { --border-width: 1.5px; --border: var(--border-active); } .url-input.clearing { --close-background: transparent; --clear-arrow-stroke: var(--close-light); } .url-input .clear { -webkit-appearance: none; position: relative; z-index: 1; padding: 0; margin: 12px 12px 12px 0; border: none; outline: none; background: var(--b, transparent); -webkit-transition: background .2s; transition: background .2s; border-radius: 6px; opacity: var(--clear-opacity); -webkit-transform: scale(var(--clear-scale)) translateZ(0); transform: scale(var(--clear-scale)) translateZ(0); } .url-input .clear:before { content: ''; position: absolute; top: 0; bottom: 0; right: 12px; left: var(--clear-swipe-left); background: var(--background); -webkit-transform-origin: 100% 50%; transform-origin: 100% 50%; -webkit-transform: translateX(var(--clear-swipe)) scaleX(var(--clear-swipe-x)) translateZ(0); transform: translateX(var(--clear-swipe)) scaleX(var(--clear-swipe-x)) translateZ(0); } .url-input .clear svg { display: block; position: relative; z-index: 1; width: 24px; height: 24px; outline: none; cursor: pointer; fill: none; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; stroke: var(--close); -webkit-transform: translateX(var(--clear-x)) rotate(var(--clear-rotate)) translateZ(0); transform: translateX(var(--clear-x)) rotate(var(--clear-rotate)) translateZ(0); } .url-input .clear svg path { -webkit-transition: stroke .2s; transition: stroke .2s; } .url-input .clear svg path.arrow { stroke: var(--clear-arrow-stroke, var(--close)); stroke-dasharray: 4px; stroke-dashoffset: var(--clear-arrow-offset); opacity: var(--clear-arrow-o); -webkit-transform: translate(var(--clear-arrow-x), var(--clear-arrow-y)) translateZ(0); transform: translate(var(--clear-arrow-x), var(--clear-arrow-y)) translateZ(0); } .url-input .clear svg path.arrow:last-child { stroke-dashoffset: var(--clear-arrow-offset-second); } .url-input .clear svg path.line { stroke-dasharray: var(--clear-line-array) 28.5px; stroke-dashoffset: var(--clear-line-offset); } .url-input .clear svg path.long { stroke: var(--clear-arrow-stroke, var(--close)); stroke-dasharray: var(--clear-long-array) 15.5px; stroke-dashoffset: var(--clear-long-offset); opacity: var(--clear-arrow-o); -webkit-transform: translate(var(--clear-arrow-x), var(--clear-arrow-y)) translateZ(0); transform: translate(var(--clear-arrow-x), var(--clear-arrow-y)) translateZ(0); } .url-input .clear:hover { --b: var(--close-background); }
4. Load the gasp animation library for smooth animations.
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.4.0/gsap.min.js"></script>
5. The core JavaScript to activate the URL input.
const { to, set, timeline } = gsap function validURL(str) { let pattern = new RegExp('^(https?:\\/\\/)?'+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ '((\\d{1,3}\\.){3}\\d{1,3}))'+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ '(\\?[;&a-z\\d%_.~+=-]*)?'+ '(\\#[-a-z\\d_]*)?$','i') return !!pattern.test(str) } function delay(fn, ms) { let timer = 0 return function(...args) { clearTimeout(timer) timer = setTimeout(fn.bind(this, ...args), ms || 0) } } document.querySelectorAll('.url-input').forEach(elem => { let icon = elem.querySelector('.icon'), favicon = icon.querySelector('.favicon'), clear = elem.querySelector('.clear'), input = elem.querySelector('input'), { classList } = elem, svgLine = clear.querySelector('.line'), svgLineProxy = new Proxy({ x: null }, { set(target, key, value) { target[key] = value if(target.x !== null) { svgLine.setAttribute('d', getPath(target.x, .1925)) } return true }, get(target, key) { return target[key] } }) svgLineProxy.x = 0 input.addEventListener('input', delay(e => { let bool = input.value.length, valid = validURL(input.value) to(elem, { '--clear-scale': bool ? 1 : 0, duration: bool ? .5 : .15, ease: bool ? 'elastic.out(1, .7)' : 'none' }) to(elem, { '--clear-opacity': bool ? 1 : 0, duration: .15 }) to(elem, { '--icon-offset': valid ? '24px' : '0px', duration: .15, delay: valid ? 0 : .2 }) if(valid) { if(favicon.querySelector('img')) { favicon.querySelector('img').src = 'https://f1.allesedv.com/64/' + input.value return } let img = new Image() img.onload = () => { favicon.appendChild(img) to(elem, { '--favicon-scale': 1, duration: .5, delay: .2, ease: 'elastic.out(1, .7)' }) } img.src = 'https://f1.allesedv.com/64/' + input.value } else { if(favicon.querySelector('img')) { to(elem, { '--favicon-scale': 0, duration: .15, onComplete() { favicon.querySelector('img').remove() } }) } } }, 250)) clear.addEventListener('click', e => { classList.add('clearing') set(elem, { '--clear-swipe-left': (input.offsetWidth - 44) * -1 + 'px' }) to(elem, { keyframes: [{ '--clear-rotate': '45deg', duration: .25 }, { '--clear-arrow-x': '2px', '--clear-arrow-y': '-2px', duration: .15 }, { '--clear-arrow-x': '-3px', '--clear-arrow-y': '3px', '--clear-swipe': '-3px', duration: .15, onStart() { to(svgLineProxy, { x: 3, duration: .1, delay: .05 }) } }, { '--clear-swipe-x': 1, '--clear-x': (input.offsetWidth - 32) * -1 + 'px', duration: .45, onComplete() { input.value = '' input.focus() if(favicon.querySelector('img')) { to(elem, { '--favicon-scale': 0, duration: .15, onComplete() { favicon.querySelector('img').remove() } }) to(elem, { '--icon-offset': '0px', '--icon-offset-line': '0px', duration: .15, delay: .2 }) } to(elem, { '--clear-arrow-offset': '4px', '--clear-arrow-offset-second': '4px', '--clear-line-array': '8.5px', '--clear-line-offset': '27px', '--clear-long-offset': '24px', '--clear-rotate': '0deg', '--clear-arrow-o': 1, duration: 0, delay: .7, onStart() { classList.remove('clearing') } }) to(elem, { '--clear-opacity': 0, duration: .2, delay: .55 }) to(elem, { '--clear-arrow-o': 0, '--clear-arrow-x': '0px', '--clear-arrow-y': '0px', '--clear-swipe': '0px', duration: .15 }) to(svgLineProxy, { x: 0, duration: .45, ease: 'elastic.out(1, .75)' }) } }, { '--clear-swipe-x': 0, '--clear-x': '0px', duration: .4, delay: .35 }] }) to(elem, { '--clear-arrow-offset': '0px', '--clear-arrow-offset-second': '8px', '--clear-line-array': '28.5px', '--clear-line-offset': '57px', '--clear-long-offset': '17px', duration: .2 }) }) }) function getPoint(point, i, a, smoothing) { let cp = (current, previous, next, reverse) => { let p = previous || current, n = next || current, o = { length: Math.sqrt(Math.pow(n[0] - p[0], 2) + Math.pow(n[1] - p[1], 2)), angle: Math.atan2(n[1] - p[1], n[0] - p[0]) }, angle = o.angle + (reverse ? Math.PI : 0), length = o.length * smoothing; return [current[0] + Math.cos(angle) * length, current[1] + Math.sin(angle) * length]; }, cps = cp(a[i - 1], a[i - 2], point, false), cpe = cp(point, a[i - 1], a[i + 1], true); return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}`; } function getPath(x, smoothing) { return [ [2, 2], [12 - x, 12 + x], [22, 22] ].reduce((acc, point, i, a) => i === 0 ? `M ${point[0]},${point[1]}` : `${acc} ${getPoint(point, i, a, smoothing)}`, '') }