Animated copy to clipboard button with Framer Motion and SVG
Created Sep 26 2020
JS1import { css } from '@emotion/core';2import { motion, useMotionValue, useTransform } from 'framer-motion';3import React from 'react';45export const CopyToClipboardButton = props => {6const duration = 0.4;7const boxVariants = {8hover: isChecked => ({9scale: 1.05,10strokeWidth: 3,11opacity: isChecked ? 0 : 1,12}),13pressed: isChecked => ({14scale: 0.95,15strokeWidth: 1,16opacity: isChecked ? 0 : 1,17}),18checked: { opacity: 0 },19unchecked: { stroke: '#949699', strokeWidth: 2, opacity: 1 },20};2122const tickVariants = {23pressed: isChecked => ({ pathLength: isChecked ? 0.85 : 0.05 }),24checked: { pathLength: 1 },25unchecked: { pathLength: 0 },26};2728const [isChecked, setIsChecked] = React.useState(false);29const pathLength = useMotionValue(0);30const opacity = useTransform(pathLength, [0.05, 0.15], [0, 1]);3132const copyToClipboard = content => {33const el = document.createElement(`textarea`);34el.value = content;35el.setAttribute(`readonly`, ``);36el.style.position = `absolute`;37el.style.left = `-9999px`;38document.body.appendChild(el);39el.select();40document.execCommand(`copy`);41document.body.removeChild(el);42};4344React.useEffect(() => {45if (isChecked) {46setTimeout(() => setIsChecked(false), 3000);47}48}, [isChecked]);4950return (51<button52css={css`53background: transparent;54border: none;55height: 25px;56cursor: ${isChecked ? 'default' : 'pointer'};57outline: none;58`}59aria-label="Copy to clipboard"60title="Copy to clipboard"61disabled={isChecked}62onClick={() => {63copyToClipboard(props.text);64setIsChecked(true);65}}66>67<motion.svg68initial={false}69animate={isChecked ? 'checked' : 'unchecked'}70whileHover="hover"71whileTap="pressed"72transition={{ duration }}73width="25"74height="25"75viewBox="0 0 25 25"76fill="none"77xmlns="http://www.w3.org/2000/svg"78>79<motion.path80d="M20.8511 9.46338H11.8511C10.7465 9.46338 9.85107 10.3588 9.85107 11.4634V20.4634C9.85107 21.5679 10.7465 22.4634 11.8511 22.4634H20.8511C21.9556 22.4634 22.8511 21.5679 22.8511 20.4634V11.4634C22.8511 10.3588 21.9556 9.46338 20.8511 9.46338Z"81stroke="black"82strokeWidth="2"83strokeLinecap="round"84strokeLinejoin="round"85variants={boxVariants}86custom={isChecked}87transition={{ duration }}88/>89<motion.path90d="M5.85107 15.4634H4.85107C4.32064 15.4634 3.81193 15.2527 3.43686 14.8776C3.06179 14.5025 2.85107 13.9938 2.85107 13.4634V4.46338C2.85107 3.93295 3.06179 3.42424 3.43686 3.04917C3.81193 2.67409 4.32064 2.46338 4.85107 2.46338H13.8511C14.3815 2.46338 14.8902 2.67409 15.2653 3.04917C15.6404 3.42424 15.8511 3.93295 15.8511 4.46338V5.46338"91stroke="black"92strokeWidth="2"93strokeLinecap="round"94strokeLinejoin="round"95variants={boxVariants}96custom={isChecked}97transition={{ duration }}98/>99<motion.path100d="M20 6L9 17L4 12"101stroke="#949699"102strokeWidth="2"103strokeLinecap="round"104strokeLinejoin="round"105variants={tickVariants}106style={{ pathLength, opacity }}107custom={isChecked}108transition={{ duration }}109/>110</motion.svg>111</button>112);113};