@iwanagashun

Javascriptのことを何も知らないせいで美しいコードを書けない人が助けを求めている!

意見交換

先日、自分のWebサイトを公開するにあたり、「ボタンをクリックしたらメールアドレスがクリップボードにコピーされる」「コピー後にツールチップ的な見た目の通知がボタンの周囲に出現して消える」という処理をJavascriptで書きました。

書きましたといってもJavascriptはぺーぺーです。要素を取得して表示したり消したりができる程度です。これまで(独学ですが)やってきたHTMLやCSSと違って調べても……こう……理解ができない……アロー関数とは……? CSSのショートハンド的なあれですかね……?

でもちょっとこれが実装できないと仕事上困るな〜ということもあり、先人の知恵を拝借しまして、「ボタンをクリックしたらinput要素のvalueの内容を取得してクリップボードにコピーする」処理と「そのあとであらかじめhiddenにしておいた要素を表示する(hidden指定したクラスをremove/addしている)」処理と、「上記の通知をフェードインさせる」処理をそれぞれ悪魔合体させていじくりまわしました。

それがこちらになります。


document.getElementById('copyButton').addEventListener('click', () => {
 var originalCopyText = document.getElementById('originalCopyText');
 var visibilityText = document.getElementById('visibilityText');
  
   document.querySelector('#visibilityText').animate(
 [{ opacity: 0 },{ opacity: 1 }],
 { duration: 300, fill: 'forwards'}
 );

 originalCopyText.select();
 originalCopyText.setSelectionRange(0, 9999);

 navigator.clipboard.writeText(originalCopyText.value);

 visibilityText.classList.remove('message-hidden');
 visibilityText.classList.add('message-text');

 setTimeout(() => {
  visibilityText.classList.remove('message-text');
  visibilityText.classList.add('message-hidden');
 }, 800);
});

うん。

いじくりまわしすぎてもはや先人たちの努力の面影はどこにもありません。
実にひどいことをしました。礼を失する行為であると同時にソースコードへの虐待です。許してはならない。

助言をいただきたいのは、

  1. このソースコードをセマンティック且つ美しく明快にするにはどうしたらいいか
  2. 通知用の要素(.message-text)をフェードアウトさせるにはどこに処理を挿入したらいいか。

このふたつです。
どうかお助けください……。°(´ฅωฅ`)°。

0

とりあえず、HTMLとCSSはそのまま流用して、JavaScriptだけ変更する場合の一例。

const fadeTime = 350;
const delayTime = 1200;

const vt = document.getElementById('visibilityText');
const cb = document.getElementById('copyButton');
const oct = document.getElementById('originalCopyText');
const vts = vt.style;
vt.classList.add('message-text');

cb.addEventListener('click', () => {
  navigator.clipboard.writeText(oct.value);

  Object.assign(vts, {
    opacity: '0',
    display: 'block',
    transition: `${fadeTime}ms`,
  });

  setTimeout(() => {
    vts.opacity = '1';
    setTimeout(() => {
      vts.opacity = '0';
      setTimeout(() => vts.display = 'none', fadeTime);
    }, delayTime);
  });
});
0

コードには正解はないと思いますが、いくつかアドバイスできるところが見えてコメントします。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .clickcopy {
            position: relative;
        }

        .message-hide {
            display : none ;
        }

        .message-text {
            position: absolute;
            left: 50%;
            transform: translateX(-50%);
            bottom: 150%;
            padding: .5em;
            white-space: nowrap;
            font-size: .9em;
            background: #000;
            color: #fff;
            border-radius: .5em;
            opacity: 0; /* 最初から透明に設定 */
        }

        .message-text:before {
            content: '';
            position: absolute;
            top: 2.2em;
            left: 50%;
            margin-left: -6px;
            border: 6px solid transparent;
            border-top: 8px solid #000;
        }
    </style>
</head>
<body>
    <p>ツールチップがボタン上部に表示される関係で挿入されている何の変哲もないp要素</p>
    <input id="originalCopyText" type="text" value="hogehoge@example.com" hidden readonly />

    <button id="copyButton" class="clickcopy">
        メールアドレスをコピーする
        <span id="visibilityText" class="message-hide">
            コピーしました!
        </span>
    </button>
</body>
<script>

    const SETTIME = 1000 ;

    const copyButton = document.getElementById('copyButton') ;
    const visibilityText = document.getElementById('visibilityText') ;

    // message-textが出た時、クリックすると出るEvent防ぐ
    visibilityText.addEventListener('click', (event) => {
        event.stopImmediatePropagation() ;
    }) ;
    
    copyButton.addEventListener('click', () => {
        
        const originalCopyText = document.getElementById('originalCopyText') ; // varよりは「const」「let」がいい

        visibilityText.className = 'message-text' ; // classの名前 message-hide -> message-text
        
        document.querySelector('#visibilityText').animate(
            [
                { opacity: 0 },
                { opacity: 1 },
                { opacity: 0 },
            ],
            { 
                duration: SETTIME, 
                fill: 'forwards'
            }
        ) ; // javascriptのanimateはfade-inからfade-outまで一回で設定できる。

        originalCopyText.select() ;
        originalCopyText.setSelectionRange(0, 9999) ;

        navigator.clipboard.writeText(originalCopyText.value) ;

        setTimeout(() => {
            visibilityText.className = 'message-hide' ; // classの名前 message-text -> message-hide
        }, SETTIME) ;

    }) ;

</script>
</html>

.message-textを最初に透明に設定、javascriptのanimate関数でfade in, outが連続で出るように設定すればHTMLの要素を削除したり、追加したりする必要ではないです。
また、.message-textが出る時、クリックするとEventが発生する問題があったので、防ぐ処理を入れました。

varよりconst、letがいい理由は下のURLを共有しますので、参考になると幸いです。

0

@heesukim998
opacityだけで制御しようとすると、ツールチップが見えていない場合でもツールチップが透明な状態で存在している場所をクリックできてしまいます。

1

@heesukim998
そのコードの場合、フェードイン完了直後にフェードアウトが始まって、ツールチップが表示されている状態を維持する時間がありませんが、例えば

200ミリ秒でフェードイン
 ↓
2秒保持
 ↓
200ミリ秒でフェードアウト

としたい場合はどうすればよいでしょうか?

0

あなたも回答してみませんか :)

新規登録
すでにアカウントを持っている方はログイン