Skip to content

Instantly share code, notes, and snippets.

@Potherca
Last active June 24, 2023 17:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Potherca/33b4d10024f56ba0610f8e70477687cb to your computer and use it in GitHub Desktop.
Save Potherca/33b4d10024f56ba0610f8e70477687cb to your computer and use it in GitHub Desktop.

This gist contains the recipe to make a copy button.

It is activated by adding data-js="add-to-page" to an element and including the CSS and JS files.

<link rel="stylesheet" href="https://gist.pother.ca/33b4d10024f56ba0610f8e70477687cb/copy-button.css"&gt
<script async src="https://gist.pother.ca/33b4d10024f56ba0610f8e70477687cb/copy-button.js"></script&gt
<pre data-js="copy">Your Text Goes Here</pre>

The button itself is an SVG that is added to an HTML element using an ::after pseudo-element.

The JS is an EventListener on a Click event, and uses navigator.clipboard to copy the text.

Once the text is copied, the button is given a CSS class that changes the SVG to a checkmark.

Both SVG icons are taken from Material Line Icons.

Everything together is no more than 30 lines of CSS and JS!

It can be seen in action at https://gist.pother.ca/33b4d10024f56ba0610f8e70477687cb

image

/* <textarea> does not support an `::after` pseudo-element so some selector wrangling is needed */
[data-js~="copy"]:not(textarea),
:has(> textarea[data-js~="copy"]) {
padding-right: 2em;
position: relative;
transition: all 1s ease-in-out;
}
[data-js~="copy"]:not(textarea)::after,
:has(> textarea[data-js~="copy"])::after {
/* https://icon-sets.iconify.design/line-md/clipboard-arrow/ */
content: url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" viewBox="0 0 24 24"%3E%3Cg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"%3E%3Cg stroke-width="2"%3E%3Cpath stroke-dasharray="16" stroke-dashoffset="16" d="M12 3H19V11"%3E%3Canimate fill="freeze" attributeName="stroke-dashoffset" dur="0.2s" values="16%3B0"%2F%3E%3C%2Fpath%3E%3Cpath stroke-dasharray="44" stroke-dashoffset="44" d="M19 17V21H5V3H12"%3E%3Canimate fill="freeze" attributeName="stroke-dashoffset" begin="0.2s" dur="0.4s" values="44%3B0"%2F%3E%3C%2Fpath%3E%3Cpath stroke-dasharray="10" stroke-dashoffset="10" d="M21 14H12.5"%3E%3Canimate fill="freeze" attributeName="stroke-dashoffset" begin="1s" dur="0.2s" values="10%3B0"%2F%3E%3C%2Fpath%3E%3Cpath stroke-dasharray="6" stroke-dashoffset="6" d="M12 14L15 17M12 14L15 11"%3E%3Canimate fill="freeze" attributeName="stroke-dashoffset" begin="1.2s" dur="0.2s" values="6%3B0"%2F%3E%3C%2Fpath%3E%3C%2Fg%3E%3Cpath stroke-dasharray="12" stroke-dashoffset="12" d="M14.5 3.5V6.5H9.5V3.5"%3E%3Canimate fill="freeze" attributeName="stroke-dashoffset" begin="0.7s" dur="0.2s" values="12%3B0"%2F%3E%3C%2Fpath%3E%3C%2Fg%3E%3C%2Fsvg%3E');
backdrop-filter: blur(10px);
cursor: pointer;
opacity: 0.35;
position: absolute;
right: 0.15em;
top: 0.15em;
width: 1.35em;
}
[data-js~="copy"]:not(textarea):hover::after,
:has(> textarea[data-js~="copy"]):hover::after {
opacity: 1;
}
[data-js~="copy"]:not(textarea).copied::after,
:has(> textarea[data-js~="copy"]).copied::after {
/* https://icon-sets.iconify.design/line-md/clipboard-check-twotone/ */
content: url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" viewBox="0 0 24 24"%3E%3Cpath fill="lime" fill-opacity="0" d="M6 4H10V6H14V4H18V20H6V4Z"%3E%3Canimate fill="freeze" attributeName="fill-opacity" begin="1.2s" dur="0.15s" values="0%3B0.3"%2F%3E%3C%2Fpath%3E%3Cg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"%3E%3Cg stroke-width="2"%3E%3Cpath stroke-dasharray="66" stroke-dashoffset="66" d="M12 3H19V21H5V3H12Z"%3E%3Canimate fill="freeze" attributeName="stroke-dashoffset" dur="0.6s" values="66%3B0"%2F%3E%3C%2Fpath%3E%3Cpath stroke-dasharray="10" stroke-dashoffset="10" d="M9 13L11 15L15 11"%3E%3Canimate fill="freeze" attributeName="stroke-dashoffset" begin="1s" dur="0.2s" values="10%3B0"%2F%3E%3C%2Fpath%3E%3C%2Fg%3E%3Cpath stroke-dasharray="12" stroke-dashoffset="12" d="M14.5 3.5V6.5H9.5V3.5"%3E%3Canimate fill="freeze" attributeName="stroke-dashoffset" begin="0.7s" dur="0.2s" values="12%3B0"%2F%3E%3C%2Fpath%3E%3C%2Fg%3E%3C%2Fsvg%3E');
fill: lime;
opacity: 1;
}
:has(> textarea[data-js~="copy"]) {
display: block;
width: calc(100% + 2.05em);
}
document.addEventListener("readystatechange", (event) => {
let loaded = false;
if (
loaded === false
&& (
event.target.readyState === "interactive"
|| event.target.readyState === "complete"
)
) {
document.querySelectorAll('[data-js~="copy"]').forEach(element => element.addEventListener('click', event => {
let target = event.currentTarget
let subject
if (target.dataset.js.includes('copy-html')) {
subject = target.innerHTML
} else {
if (target.tagName.toLowerCase() === 'textarea') {
subject = target.value
target = target.parentElement
} else {
subject = target.innerText
}
}
navigator.clipboard.writeText(subject)
.then(() => {
console.log('Copied!');
target.classList.add('copied');
setTimeout(() => target.classList.remove('copied'), 2200);
});
}))
loaded = true;
}
});
<!doctype html>
<html lang="en">
<meta charset="UTF-8">
<title>Copy Button</title>
<link rel="icon" href="https://favicon.potherca.workers.dev/36" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<!-- HTML Meta Tags -->
<title>Copy Button</title>
<meta name="description" content="CSS + JS Copy Button">
<!-- Facebook Meta Tags -->
<meta property="og:url" content="https://gist.pother.ca/33b4d10024f56ba0610f8e70477687cb">
<meta property="og:type" content="website">
<meta property="og:title" content="Copy Button">
<meta property="og:description" content="CSS + JS Copy Button">
<meta property="og:image" content="https://user-images.githubusercontent.com/195757/236698925-4969f801-61fb-4e16-81ae-0d8c8893a9d2.png">
<!-- Twitter Meta Tags -->
<meta name="twitter:card" content="summary_large_image">
<meta property="twitter:domain" content="gist.pother.ca">
<meta property="twitter:url" content="https://gist.pother.ca/33b4d10024f56ba0610f8e70477687cb">
<meta name="twitter:title" content="Copy Button">
<meta name="twitter:description" content="CSS + JS Copy Button">
<meta name="twitter:image" content="https://user-images.githubusercontent.com/195757/236698925-4969f801-61fb-4e16-81ae-0d8c8893a9d2.png">
<!-- Meta Tags Generated via https://www.opengraph.xyz -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/picnic">
<link rel="stylesheet" href="https://pother.ca/CssBase/css/created-by-potherca.css">
<link rel="stylesheet" href="https://gist.pother.ca/33b4d10024f56ba0610f8e70477687cb/copy-button.css">
<script async src="https://gist.pother.ca/33b4d10024f56ba0610f8e70477687cb/copy-button.js"></script>
<style>
article {
background-color: #fff;
border-bottom-left-radius: 0.5em;
border-bottom-right-radius: 0.5em;
}
body {
background-color: #def;
}
pre {
display: block;
margin: 0.5em;
padding: 0.5em;
white-space: pre-wrap;
}
form {
text-align: center;
}
h1 {
width: 10em
}
.created-by {
padding: 0.5em;
text-align: center;
width: 100%;
}
</style>
<article class="flex two-third-500 off-sixth-500">
<header class="full">
<h1 data-js="copy">Copy Button</h1>
</header>
<main class="full">
<p>
This page contains the recipe to make a copy button.
</p>
<p>
It is activated by adding <code>data-js="copy"</code> to an element and including the CSS and JS files.
</p>
<p>
The button itself is an SVG that is added to an HTML element using
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::after">an <code>::after</code> pseudo-element</a>.
</p>
<p>
The JS is an
<a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener">EventListener</a> on a Click event, and uses
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/clipboard"><code>navigator.clipboard</code></a> to copy the text.
</p>
<p>
Once the text is copied, the button is given a CSS class that changes the SVG to a checkmark.
</p>
<p>
Both SVG icons are taken from <a href="https://cyberalien.github.io/line-md/">Material Line Icons</a>.
</p>
<p>
Everything together is no more than 30 lines of CSS and JS!
</p>
<p>
It can be seen in action below.
</p>
<pre data-js="copy">
&lt;link rel="stylesheet" href="https://gist.pother.ca/33b4d10024f56ba0610f8e70477687cb/copy-button.css"&gt
&lt;script async src="https://gist.pother.ca/33b4d10024f56ba0610f8e70477687cb/copy-button.js"&gt;&lt;/script&gt
&lt;pre data-js="copy"&gt;Your Text Goes Here&lt;/pre&gt;
</pre>
</main>
</article>
<footer class="created-by">
<p>
Source of this page is
available on <a href="https://gist.github.com/Potherca/33b4d10024f56ba0610f8e70477687cb">Github</a>
under a <a rel="license" href="https://spdx.org/licenses/MPL-2.0.html"
>Mozilla Public License 2.0</a> &mdash; Created by <a href="https://pother.ca/" class="potherca">Potherca</a>
</p>
</footer>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment