skip to Main Content

I have a Share Icon that looks like:

share-icon

I want the first 5 icons to be shown. The 5th icon being the share icon.

I want the rest of the icons to be under share icon & when someone taps or hovers over it (either tap or hover as I don’t know what would be best UX in this case), it should open to the right at their usual positions.

SocialShare.jsx

import React from 'react';
import { motion } from 'framer-motion';
import { ShareIcon } from '@heroicons/react/solid';

import {
  Facebook,
  HackerNews,
  Reddit,
  Twitter,
  LinkedIn,
  Pinterest,
  Telegram,
  Whatsapp,
  Pocket
} from '../icons/index';

const isProduction = process.env.NODE_ENV === 'production';

export const SocialShare = ({ title, slug }) => {
  const [share, openShare] = React.useState(false);
  const [host, setHost] = React.useState('');

  React.useEffect(() => {
    setHost(window.location.host);
  }, []);

  const url = `${isProduction ? 'https://' : 'http://'}${host}/${slug}`;
  const text = title + url;
  const via = 'deadcoder0904';
  const sharer = {
    facebook: `https://www.facebook.com/sharer/sharer.php?u=${url}`,
    twitter: `https://twitter.com/intent/tweet?url=${url}&text=${text}&via=${via}`,
    reddit: `https://www.reddit.com/submit?title=${title}&url=${url}`,
    hackernews: `https://news.ycombinator.com/submitlink?u=${url}&t=${title}`,
    linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${url}`,
    pinterest: `https://pinterest.com/pin/create/button/?url=${url}&description=${title}`,
    telegram: `https://telegram.me/share/url?url=${url}&text=${text}`,
    whatsapp: `https://wa.me/?text=${title}%0D%0A${url}%0D%0A%0D%0A${text}`,
    pocket: `https://getpocket.com/edit.php?url=${url}`
  };

  const variants = {
    hidden: {
      opacity: 0,
      translateX: -16
    },
    visible: {
      opacity: 1,
      translateX: 0
    }
  };

  return (
    <ul className="flex items-center mt-8 space-x-4">
      <li>
        <a className="" href={sharer.facebook} title="Share on Facebook">
          <Facebook />
        </a>
      </li>
      <li>
        <a className="" href={sharer.twitter} title="Share on Twitter">
          <Twitter />
        </a>
      </li>
      <li>
        <a className="" href={sharer.reddit} title="Share on Reddit">
          <Reddit />
        </a>
      </li>
      <li>
        <a className="" href={sharer.hackernews} title="Share on Hacker News">
          <HackerNews />
        </a>
      </li>
      <motion.li className="cursor-pointer" whileHover={{}}>
        <ShareIcon
          className="w-6 h-6 text-gray-300"
          onClick={() => {
            openShare(!share);
          }}
        />
      </motion.li>

      <motion.li
        className=""
        initial="hidden"
        animate="visible"
        variants={variants}
        transition={{
          type: 'tween',
          ease: 'easeInOut'
        }}
      >
        <a className="" href={sharer.linkedin} title="Share on LinkedIn">
          <LinkedIn />
        </a>
      </motion.li>
      <li>
        <a className="" href={sharer.pinterest} title="Share on Pinterest">
          <Pinterest />
        </a>
      </li>
      <li>
        <a className="" href={sharer.telegram} title="Share on Telegram">
          <Telegram />
        </a>
      </li>
      <li>
        <a className="" href={sharer.whatsapp} title="Share on Whatsapp">
          <Whatsapp />
        </a>
      </li>
      <li>
        <a className="" href={sharer.pocket} title="Share on Pocket">
          <Pocket />
        </a>
      </li>
    </ul>
  );
};

The icons are in a flex container so I can’t use staggerChildren. Stagger Children is exactly what I’m looking for but I can’t find a solution to this using Flexbox?

Do I need to change the DOM elements by adding a wrapper? But that would break the ul>li combo.

All I want is it all the icons open when I hover on share to the right & when I stop hovering, they come back under share icon & disappear. Basically, share icon functionality like https://codepen.io/romswellparian/full/mJXdqV:

animated share gif

It only should be to the right & first 4 icons should be always shown.

I’ve made a complete Stackblitz reproduction → https://stackblitz.com/edit/share-animation-framer-motion?file=components%2FSocialShare.jsx

2

Answers


  1. Stagger Children

    Flexbox shouldn’t prevent you from using staggerChildren.

    To use staggerChildren you add it to the transition property of the parent variants. That means the parent of your list items needs to be a motion component, as do the list items you want to animate.

    const listVariants = {
      hidden: {
        transition: {
          staggerChildren: 0.1,
          staggerDirection: -1
        }
      },
      visible: {
        transition: {
          staggerChildren: 0.1
        }
      }
    };
    
    const itemVariants = {
      hidden: {
        opacity: 0,
        x: -16
      },
      visible: {
        opacity: 1,
        x: 0
      }
    };
    
    return (
        <motion.ul
          variants={listVariants}
          initial="hidden"
          animate={share ? 'visible' : 'hidden'}
        >
          <motion.li variants={itemVariants}>
            <a href={sharer.linkedin} title="Share on LinkedIn">
              <LinkedIn />
            </a>
          </motion.li>
          {/* ...etc */}
        </motion.ul>
    );
    

    Here’s a quick fork of your project that does what you’re describing:
    https://stackblitz.com/edit/share-animation-framer-motion-e5fp5p?file=components/SocialShare.jsx

    Animate Presence

    If you actually want to remove the items from the DOM when they are hidden, then you need to use AnimatePresence. This requires that you wrap all the items what will enter and exit the DOM in the <AnimatePresence> tag. Each item will need an initial, animate and exit variant that describes the animation, and each item needs a unique key. The last one is easy to forget.

    Unfortunately, I don’t think StaggerChildren works with AnimatePresence in the way you want it to. To stagger the enter and exit animations you can use Dynamic Variants with a custom prop to define the animation delay for each item.

    Short example:

    const itemVariants = {
      hidden: i => ({
        opacity: 0,
        x: -16,
        transition: {
          delay: i * 0.1
        }
      }),
      visible: i => ({
        opacity: 1,
        x: 0,
        transition: {
          delay: i * 0.1 // custom prop used to stagger delay
        }
      })
    };
    
    return (
      <ul className="flex items-center mt-8 space-x-4">
        <AnimatePresence>
          {share && (<>
            <motion.li
              variants={itemVariants}
              key="linkedin"    /* don't forget key! */
              initial="hidden"
              animate="visible"
              exit="hidden"
              custom={1}  /* custom prop used to stagger delay */
            >
              <a href={sharer.linkedin} title="Share on LinkedIn">
                <LinkedIn />
              </a>
            </motion.li>
    
            <motion.li
              variants={itemVariants}
              key="pinterest"
              initial="hidden"
              animate="visible"
              exit="hidden"
              custom={2}
            >
              <a href={sharer.pinterest} title="Share on Pinterest">
                <Pinterest />
              </a>
            </motion.li>
            {/* etc... */}
          </>)}
        </AnimatePresence>
      </ul>
    )
    

    Here’s the StackBlitz with AnimatePresence:
    https://stackblitz.com/edit/share-animation-framer-motion-wjdxhc?file=components/SocialShare.jsx

    The way your example is set up, removing elements from the <ul> container causes it to shift (since the container size changes). I fixed that by setting an explicit width, but you could also change the flex alignment of float it or whatever is going to work with your actual project.

    Login or Signup to reply.
  2. Following on from @Cadin answer I’ve found you can use both staggerChildren and AnimatePresense to provide a smoother animation when rendering lists.

    const staggerChildrenVariant = {
      animate: {
        transition: {
          staggerChildren: 0.5,
        },
      }
    };
    
    const childVariant = {
     hidden: { height: 0 },
     visible: { height: 'auto' }
    };
    
    <motion.div variants={staggerChildrenVariant} initial="animate" animate="animate">
      <AnimatePresence>
        {arr.map((arrItem) => (
          <motion.div
            key={arrItem.id}
            variants={childVariant}
            initial="hidden"
            animate="visible"
            exit="hidden"
          >
            ...
          </motion.div>
        ))}
      </AnimatePresence>
    </motion.div>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search