LoginSignup
1
1

More than 1 year has passed since last update.

【React】アコーディオン風に飛び出す3Dリンクボタンを実装する

Posted at

概要

アコーディオン風に飛び出す3Dリンクボタンを実装します。

output(video-cutter-js.com) (2).gif

本記事は、以下の動画をReactで実装したものです。

スタイリングには、emotion/css(CSS in JS)を使用しています。

アイコンは、react-iconsを使用しています。

実装

import React, { useRef, VFC } from 'react';
import { FaFacebookF, FaInstagram, FaLinkedinIn, FaTwitter, FaWhatsapp } from 'react-icons/fa';
import { IconType } from 'react-icons/lib';
import { css } from '@emotion/css';

export const IsometricSocialMediaIcon: VFC = () => {
    const items: { href: string; icon: IconType; themeColor: string }[] = [
        { href: '#facebook', icon: FaFacebookF, themeColor: '#3b5999' },
        { href: '#twitter', icon: FaTwitter, themeColor: '#55acee' },
        { href: '#whatsapp', icon: FaWhatsapp, themeColor: '#25d366' },
        { href: '#linkedin', icon: FaLinkedinIn, themeColor: '#0077b5' },
        { href: '#instagram', icon: FaInstagram, themeColor: '#e4405f' }
    ]

    return (
        <div className={styles.container}>
            <ul className={styles.list}>
                {items.map((item, i) => (
                    <ListItem key={i} href={item.href} icon={<item.icon />} themeColor={item.themeColor} />
                ))}
            </ul>
        </div>
    )
}

// ==============================================

type ListItemProps = {
    href: string
    icon: React.ReactNode
    themeColor: string
}

const ListItem: VFC<ListItemProps> = props => {
    const { href, icon, themeColor } = props

    const layer1Ref = useRef<HTMLSpanElement>(null)
    const layer2Ref = useRef<HTMLSpanElement>(null)
    const layer3Ref = useRef<HTMLSpanElement>(null)
    const layer4Ref = useRef<HTMLSpanElement>(null)

    const hoverHandler = () => {
        layer1Ref.current!.classList.toggle('hover')
        layer2Ref.current!.classList.toggle('hover')
        layer3Ref.current!.classList.toggle('hover')
        layer4Ref.current!.classList.toggle('hover')
    }

    return (
        <li className={styles.item(themeColor)} onMouseEnter={hoverHandler} onMouseLeave={hoverHandler}>
            <a href={href}>
                <span ref={layer1Ref} className={styles.layer(themeColor)}></span>
                <span ref={layer2Ref} className={styles.layer(themeColor)}></span>
                <span ref={layer3Ref} className={styles.layer(themeColor)}></span>
                <span ref={layer4Ref} className={styles.layer(themeColor)}>
                    {icon}
                </span>
            </a>
        </li>
    )
}

// ==============================================
// styles

const styles = {
    container: css`
        position: relative;
        width: 100%;
        height: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: #222;
    `,
    list: css`
        position: relative;
        display: flex;
        transform-style: preserve-3d;
        transform: rotate(-25deg) skew(25deg);
    `,
    item: (themeColor: string) => css`
        position: relative;
        list-style: none;
        width: 60px;
        height: 60px;
        margin: 0 10px;

        &::before {
            content: '';
            position: absolute;
            bottom: -10px;
            left: 0;
            width: 100%;
            height: 10px;
            background-color: #2a2a2a;
            transform-origin: top;
            transform: skewX(-41deg);
            transition: 0.5s;
        }
        &::after {
            content: '';
            position: absolute;
            top: 0;
            left: -9px;
            width: 9px;
            height: 100%;
            background-color: #2a2a2a;
            transform-origin: right;
            transform: skewY(-49deg);
            transition: 0.5s;
        }

        &:hover::before,
        &:hover::after {
            opacity: 0.2;
            background-color: ${themeColor};
        }
    `,
    layer: (hoveredColor: string) => css`
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: #333;
        color: rgba(255, 255, 255, 0.2);
        font-size: 30px;
        transition: 0.2s;

        &.hover {
            z-index: 1000;
            transition: 0.5s;
            color: #fff;
            box-shadow: -1px 1px 1px rgba(0, 0, 0, 0.05);
            background-color: ${hoveredColor};

            &:nth-child(5) {
                transform: translate(40px, -40px);
                opacity: 1;
            }
            &:nth-child(4) {
                transform: translate(30px, -30px);
                opacity: 0.8;
            }
            &:nth-child(3) {
                transform: translate(20px, -20px);
                opacity: 0.6;
            }
            &:nth-child(2) {
                transform: translate(10px, -10px);
                opacity: 0.4;
            }
            &:nth-child(1) {
                transform: translate(0px, 0px);
                opacity: 0.2;
            }
        }
    `
}
  • CSSでの3Dは、transform-stylepreserve-3dを指定することで表現できます。
list: css`
    position: relative;
    display: flex;
    transform-style: preserve-3d;
    transform: rotate(-25deg) skew(25deg);
`

  • 親要素のhoverについて

親要素がhoverされたときに、子要素に特定のスタイルを割り当てようとする場合、バニラのCSSだと以下のように記述できます。

li:hover span {
...
}

CSS in JSでは、タグごとにスタイルを割り当てるため、他のタグの状態を知ることはできません。(私が記述方法を知らないだけかもしれませんが...)
なので、親要素がhoverされたタイミングで、アクションを起こしたい要素にクラスを割り当てる必要があります。

const hoverHandler = () => {
    layer1Ref.current!.classList.toggle('hover')
    layer2Ref.current!.classList.toggle('hover')
    layer3Ref.current!.classList.toggle('hover')
    layer4Ref.current!.classList.toggle('hover')
}

これをスタイル内で呼びだして、親要素(li tag)がhoverされたときの子要素(span tag)の動作を記述します。

layer: (hoveredColor: string) => css`
    ・・・
    &.hover {
        ・・・
    }
`

もしくは、hover時のスタイルを用意して、親要素がhoverされたときに、子要素にhover時のスタイルを割り当てるようにします。

この処理は、以下を参考にしてください。
【React】emotion/css の使い方メモ/classの動的な追加による遷移(transition)/スタイルの統合(cx)を使った書き方

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1