reduceの使い方について調べました【Javascript】

javascript


今回、javascriptのreduceについてまとめたのでメモ。
reduceを使って文字のアニメーションを実装するサンプルも作成しました。

reduceはどんなことができるか

reduceは配列の値を順番に処理するメソッドです。ちなみにreduceは英語で「まとめる」という意味があります。
その言葉通り、コールバック関数で返ってきた値を累積して1つの値にまとめます。

基本構文は次の通りです。

const result = arr.reduce(callback( accumulator, currentValue, index, ary), defaultValue);

変数resultに累積した値が格納されます。
引数の詳細は次の通りです。

accumulator: callbackの戻り値
currentValue: 現在処理している値
index: インデックス
ary: reduceを実行している配列
defaultValue: callback関数の初期値。省略すると配列の最初の値が設定される

※indexとary、defaultValueは省略可能です。

配列の値を順番に足していく

次は、配列の値を順番に足していくコードです。
それぞれの値を表で出力しました。
最終的にはすべての値を足し合わせた28の値が返却されます。

See the Pen recude-example by donguri2020 (@m-ke) on CodePen.

以下は、reduce部分を抜き出したコードです。

const ary = [1, 2, 3, 4, 5, 6, 7, 8];
const result = ary.reduce((accu, curr, index, ary) => {
    return accu + curr;
});

console.log(result) //28

ポイントはreturn accu + curr;の部分です。こちらで返された値が次のaccuに格納されます。
初期値が設定されていない場合、配列の最初の値がaccuに格納されます。

配列の最大値を求める

reduceで配列の最大値を求めてみます。

See the Pen by donguri2020 (@m-ke) on CodePen.

配列の数字を順番に比較し、大きい値を戻り値としてaccuに返します。
以下は、reduceの部分を抜き出したコードです。

const ary = [-20,0,4,100,8,24,9,3,-1,9,3];

const maxNum = ary.reduce((accu, curr) => {
  if(curr > accu) {
    accu = curr;
  } 
  return accu;
}, 0)

console.log(maxNum) // 100

初期値を設定しない場合、配列の1番目がaccuに格納されます。配列に設定する値によってどんな数字が入ってくるか分からないので、初期値「0」を設定しておきます。

accuの値が、現在処理している値currより大きい数なら改めてaccuの値を戻り値として返します。

処理した値を新しい配列で返す

配列の各値を2倍にして、新しい配列として返します。
arry.map()と同じような動きをします。

See the Pen reduce-map by donguri2020 (@m-ke) on CodePen.

以下は、reduceの部分を抜き出したコードです。

const ary = [1, 2, 3, 4, 5, 6, 7];

const resultAry = ary.reduce((accu, curr) => {
  accu.push(curr * 2);
  return accu;
  
}, []);

console.log(aryValue); // [1,2,3,4,5,6,7]
console.log(resultAry); // [2,4,6,8,10,12,14]

ポイントは初期値に配列を指定することです。
そうすることでaccuを配列として扱うことができます。

コールバック関数で、現在処理しているcurrの値を2倍にしています。
currの値をaccu(配列)にpush()メソッドで追加します。最終的には戻り値として配列accuを返します。

これを応用すればarry.fileter()に似た動作も実装できそうです。

文字をアニメーションさせるサンプル

reduceを使って、文字のアニメーションを実装しました。

実際の動作はこちらでご確認ください!(別ウィンドウが開きます)

再生ボタンを押すと文字がアニメーションします。

ソースコードを確認する(全体)

HTML

<div class="content">
    <p class="txt-animation">NOW LOADING....</p>
</div>

<button id="startBtn" class="play btn"></i></button>

SCSS

.content {
    background-color:purple;
    position: relative;
    height: 50vh;
}
.txt-animation {
    width: 100%;
    text-align: center;
    font-family: 'Alfa Slab One', cursive;
    color: #fff;
    font-size: 1.3em;
    letter-spacing: .2em;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

<!-- 文字のアニメーション設定 -->
.letter {
    display: inline-block;
    .start & {
        animation-name: kf-animation;
        animation-duration: 0.8s;
        animation-iteration-count: infinite;
        animation-fill-mode: both;
        
        @for $i from 2 to 20 {
            &:nth-child(#{$i}) {
                animation-delay: $i * 0.05s;
            }
            
        }
    }
}
<!-- 開始ボタン -->
#startBtn {
    cursor: pointer;
    color: #fff;
    font-size: 1.3em;
    font-weight: bold;
    letter-spacing: .3em;
    padding:.8em 3em;
    border-radius: 100vh;
    background-color: gray;
    &::before {
        font-family: "Font Awesome 5 Free";
        content:"\f28d";
    }
    &::after {
        content:"停止";
    }
    &.play {
        background-color: green;
        &::before {
            font-family: "Font Awesome 5 Free";
            content:"\f144";
        }
        &::after {
            content:"再生";
        }
    }
}

.btn {
    background-color: transparent;
    border: none;
    cursor: pointer;
    outline: none;
    padding: 0;
    appearance: none;
    display: block;
    transition: all 1s ease;
    margin: 20px auto;
  &:hover {
    opacity: .5;
  }
}

<!-- アニメーションキーフレーム -->
@keyframes kf-animation {
    0% {
        transform: translateY(0);

    }
    50% {
        transform: translateY(-30%);
    }
    100% {
        transform: translateY(0);
    }
}

Javascript

const startBtn = getElm("#startBtn");
const iconElm = getElm(".icon");
const el = getElm(".txt-animation");
const strAry = el.textContent.split("");

// 文字に<span>を追加
el.innerHTML = strAry.reduce(function(prev, current) {
    current = current.replace(/\s+/, '&nbsp;');
    return `${prev}<span class="letter">${current}</span>`;
}, "")

// 再生ボタンイベント
startBtn.addEventListener('click', function() {
    this.classList.toggle("play");
    el.classList.toggle("start");
})

// 要素を取得
function getElm(el) {
    return document.querySelector(el);
}

reduceで1文字ずつタグを追加する

サンプルを見ると一文字ずつ上下に動いています。
動きのアニメーションはCSSで制御しますが、文字1つ1つに特定のタグ付けが必要です。
ベタ打ちでも問題ありませんが、せっかくなのでJavasrcriptで制御します。

関連するコードを抜き出したのがこちらです。

const el = getElm(".txt-animation");
const strAry = el.textContent.split("");

// 文字に<span>を追加
el.innerHTML = strAry.reduce(function(prev, current) {
    current = current.replace(/\s+/, '&nbsp;');
    return `${prev}<span class="letter">${current}</span>`;
}, "")

// 要素を取得
function getElm(el) {
    return document.querySelector(el);
}

まず、アニメーションしたい文字列を取得し、split()で1文字ずつ分割、そして配列に変換します。
あとはarry.reduce()で1文字ずつspan要素で囲んだ値を戻り値としてprevに返します。
最終的には文字が累積され、結果がHTMLに反映されます。

なお、初期値を設定しないと、最初の文字にspan要素が追加されません。
そこで何もない文字列として、初期値に””を指定します。

また、文字に含まれる空白も1文字として認識させたいので、reduceの現在処理している値が空白だった場合、HTMLで空白を表す特殊記号&nbsp;に置換しておきます。
そうすることでブラウザ上でも空白が再現されます。

コードは以下の部分です。

current.replace(/\s+/, '&nbsp;');

CSSでアニメーションさせる

1文字ずつにspan要素をつけたら、CSSでアニメーションを制御します。関連するコードはこちらです。

<!-- 文字のアニメーション設定 -->
.letter {
    display: inline-block;
    .start & {
        animation-name: kf-animation;
        animation-duration: 0.8s;
        animation-iteration-count: infinite;
        animation-fill-mode: both;
        
        @for $i from 2 to 20 {
            &:nth-child(#{$i}) {
                animation-delay: $i * 0.05s;
            }
        }
    }
}

<!-- アニメーションキーフレーム -->
@keyframes kf-animation {
    0% {
        transform: translateY(0);

    }
    50% {
        transform: translateY(-30%);
    }
    100% {
        transform: translateY(0);
    }
}

ポイントは、1文字ずつanimation-delayでアニメーション開始時間をずらしているところです。nth-childで何番目にどれだけずらしてアニメーションするか記載します。

SCSSだと、forで値の異なる同じCSSをいくつも量産できるので楽ちんです。
今回、20文字まで対応できるようにしました。

まとめ

今回、reduceについてまとめました。上手く使えば、配列を上手くコントロールできるので便利です。
同じことを普通にforでも再現できますが、変数の数が増えたり記述が多くなります。
reduceだとコードが短くスッキリ書けるので、今後も使っていきたいと思います!