【2022年12月仕様変更対応コード】Discordの立ち絵アニメーションOBSカスタムCSS解説

【2022年12月仕様変更対応コード】Discordの立ち絵アニメーションOBSカスタムCSS解説

2022年12月16日ごろ、Discord Streamkit Overlayの仕様が変わったらしく、各所で「OBSでぴょこぴょこさせているアバターが動かなくなった!」っていう阿鼻叫喚が起きていたようです。(私は2日後くらいに急にブログのアクセスが増えてるのに気づいて知りました)
これはOBSの不具合やアップデートの影響ではありません。
OBSでカスタムCSSを書いている人はCSSの記述を新しいHTML構造に合わせればすぐ動くようになります。
以下のYouTube動画とブログで紹介しているコードも、そのままでは動きませんので、仕様変更後のHTMLに対応したコードの解説をしたいと思います。

ちなみにHTMLのclass設計が変わっただけなので、OBSでアバターを動かすカスタムCSSの仕組み自体は、上記記事の解説のとおりで変更ありません。引き続き参考にしてください。

Discord Streamkit Overlayの変更後HTMLの構造


<div id="root">
    <div class="Voice_voiceContainer__xxxxx">
        <ul class="Voice_voiceStates__xxxxx">
            <li class="Voice_voiceState__xxxxx">
                <img class="Voice_avatar__xxxxx" src="アバターURL" alt="">
                <div class="Voice_user__xxxxx">
                    <span class="Voice_name__xxxxx" style="color: rgb(255, 255, 255); font-size: 14px; background-color: rgba(30, 33, 36, 0.95);">ユーザー名</span>
                </div>
            </li>
        </ul>
    </div>
</div>

ボイスチャンネルのul要素の中に、各ユーザーのli要素があり、その中にデフォルトアバターのimgやユーザー名が入ったdiv,spanがある…という構造自体はとくに変わっていません。
そのかわり、class名に個別の変数が割り当てられて命名されるようになりました。

挙動とは関係ないですが、12月からDiscordのカスタムフォント「gg sans」が指定されるようになりました。

カスタムCSSの完成コード(2022年12月版)

とりあえず変更後のコードから先にお見せします。
@keyframesで設定しているアニメーションのコードには何も変更ありませんが、全体を掲載しています。


/*-- アバター領域共通設定 --*/

/* デフォルトアバター非表示 */
[class*="Voice_avatar"] {
    display: none;
}

[class*="Voice_voiceStates"] {
    position: fixed;
    top: 0;
    left: 0;
    width: 1920px;
    height: 1080px;
    padding: 0!important;
}

[class*="Voice_voiceState"] {
    position: fixed;
    right: 100px;
    bottom: 0;
    width: 457px;
    height: 656px!important;
    overflow: visible;
}

[class*="Voice_user"],
[class*="Voice_user"]::before,
[class*="Voice_user"]::after,
[class*="Voice_user"] [class*="Voice_name"],
[class*="Voice_user"] [class*="Voice_name"]::before,
[class*="Voice_user"] [class*="Voice_name"]::after {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    display: block;
    width: 457px;
    height: 656px;
    background: url() 0 0 no-repeat;
}

[class*="Voice_user"] [class*="Voice_name"] {
    color: transparent!important;
    background-color: transparent!important;
}

/*-- アバター個別設定 --*/

/* base */
.Voice_voiceState__xxxxx {
    animation: base 4s linear 0s alternate infinite;
}

/* right arm */
.Voice_voiceState__xxxxx [class*="Voice_user"]::before {
    background-image: url(画像のURL);
    animation: right-arm 3s ease-in-out 0s alternate infinite;
    transform-origin: 150px 430px;
}

/* left arm */
.Voice_voiceState__xxxxx [class*="Voice_user"]::after {
    background-image: url(画像のURL);
    background-position: 7px 7px;
    animation: left-arm 3s ease-in-out 0s alternate infinite;
    transform-origin: 280px 380px;
}

/* body */
.Voice_voiceState__xxxxx [class*="Voice_name"]{
    background-image: url(画像のURL);
}

/* face */
.Voice_voiceState__xxxxx [class*="Voice_name"]::before{
    background-image: url(画像のURL);
    background-position: 0 5px;
    animation: face 3.5s ease-in-out 0s alternate infinite;
    transform-origin: 240px 370px;
}

/* mouth */
.Voice_voiceState__xxxxx [class*="Voice_name"]::after{
    background-image: url(画像のURL);
    visibility: hidden;
}

.Voice_voiceState__xxxxx [class*="Speaking"] + [class*="Voice_user"] [class*="Voice_name"]::after{
    animation: mouth 0.15s steps(2) 0s alternate infinite;
}

@keyframes base {
    0% {
        transform: rotate(-2deg);
    }
    5% {
        transform: rotate(-2deg);       
    }
    50% {
        transform: rotate(0);
    }
    95% {
        transform: rotate(2deg);
    }
    100% {
        transform: rotate(2deg);
    }
}

@keyframes right-arm {
    0% {
        transform: rotate(0);
    }
    90% {
        transform: rotate(-12deg);
    }
    100% {
        transform: rotate(-12deg);
    }
}

@keyframes face {
    0% {
        transform: rotate(-4deg);
    }
    90% {
        transform: rotate(6deg);
    }
    100% {
        transform: rotate(6deg);
    }
}

@keyframes left-arm {
    0% {
        transform: rotate(0);
    }
    90% {
        transform: rotate(4deg);
    }
    100% {
        transform: rotate(4deg);
    }
}

@keyframes mouth {
    0% {
        visibility: hidden;
    }
    100% {
        visibility: visible;
    }
}

カスタムCSS変更点の解説

デフォルトアバターの非表示


/* デフォルトアバター非表示 */
[class*="Voice_avatar"] {
    display: none;
}

以前はclassセレクタで直接.avatarと指定していました。
今回はclass名が参加しているチャンネルやユーザーによって異なるため、個別に指定しなくても良いように属性セレクタを使い、文字列の部分一致で指定します。
属性セレクタはいろいろな使い方ができてめっちゃ便利なので、その他の用法も覚えて損はありません。

ul要素、li要素のベースの設定

アバターのリストを内包している入れ物となっているコンテナの設定を少し変更します。
まずはul要素。


[class*="Voice_voiceStates"] {
    position: fixed;
    top: 0;
    left: 0;
    display: block;
    width: 1920px;
    height: 1080px;
    padding: 0!important;
}

先に紹介したimg要素と同様、class名が固定されていないので、属性セレクタで指定し直しています。

というわけでli要素も同様に、class名を変更します。


[class*="Voice_voiceState"] {
    position: fixed;
    right: 100px;
    bottom: 0;
    width: 457px;
    height: 656px!important;
    overflow: visible;
}

ここでひとつ、li要素は中身にも変更点があります。
positionプロパティをabsoluteからfixedにしてあります。
本来なら親のul要素がstaticではないので、absoluteが効くはず…なのですが、なぜか効かなくなっていたのでこうしました。
親要素の高さが指定されていない場合、子要素がabsoluteになると高さがなくなってしまって0,0地点にしか配置できない、というケースはありますが、今回のケースでは指定してあるので謎です。
li要素の子要素には効いているのに…なぜ…

CSSに詳しい人だったらpositionによる配置以外にもflexgridを使って自在にアバターを並べることができると思います。

ちなみに、li要素やimg要素といった子要素はすべて


[class*="Voice_voiceStates"] li {
}

のような感じで指定することも可能です。

コード内の全ての要素をこういった形で記述していれば、万が一class名がまた変わっても、ul要素のclass名だけ一括置換すれば済むので楽っちゃ楽です。
そうしておきたい人は全部このように置き換えてしまっても構いません。
どれが何の要素なのかもわかりやすいですしね。

webサイト制作においては、こういう書き方だとパフォーマンスが落ちるのであまり推奨されませんが、Discord Streamkit Overlayの超コンパクトなコードでそれを気にする必要はありません。

極端な話、Discord Streamkit Overlayで出力されるコードに関しては


li {
}

でも動きます。

一応このブログは解説記事なので、以前のコードとの比較もしやすいようにすべてclass名指定してあります。
(どうせまた仕様変更されたり、そのときHTML構造自体も変わったりしたら、そもそもぜんぶ書き直す必要があるかもわからんので、省力化を考えてもあまり意味がない)

ユーザー名を表示している要素の設定


[class*="Voice_user"],
[class*="Voice_user"]::before,
[class*="Voice_user"]::after,
[class*="Voice_user"] [class*="Voice_name"],
[class*="Voice_user"] [class*="Voice_name"]::before,
[class*="Voice_user"] [class*="Voice_name"]::after {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    display: block;
    width: 457px;
    height: 656px;
    background: url() 0 0 no-repeat;
}

[class*="Voice_user"] [class*="Voice_name"] {
    color: transparent!important;
    background-color: transparent!important;
}

div要素やspan要素も、同様にclass名を属性セレクタで設定しています。
先の解説にもあるように


[class*="Voice_voiceStates"] div {
}

[class*="Voice_voiceStates"] span {
}

でも動くことは動きます。(CSS初心者はこれで十分なので、わかりやすい方で作ってみてください)

アバターの個別class設定


/*-- アバター個別設定 --*/

/* base */
.Voice_voiceState__xxxxx {
    animation: base 4s linear 0s alternate infinite;
}

li要素の指定について、今まではdata-reactidカスタム属性に対してユーザーIDを指定していましたが、その手が使えなくなりました。
ユーザー複数人のアバターを、サイズを変えたりアニメーションを変えたりして別々に表示したい場合は、それぞれのclass名を調べて直接指定することになります。


.Voice_voiceState__xxxxx:nth-child(1) {
}

.Voice_voiceState__xxxxx:nth-child(2) {
}

または


li:nth-child(1) {
}

li:nth-child(2) {
}

という感じでnth-child擬似クラスを使用して、ボイチャに入っている人のうちリストの何番目かを指定する必要があります。

当初class名を指定すると書いていましたが、class名は共通でした。OBSではhas擬似クラスが使えないので、リストの順序で判定するしかありません。入退室によってメンバーの順序が入れ替わった場合、この数字を変更する必要があります。

もし、自分1人で配信していて、自分の立ち絵だけを表示する場合にはこれは必要ありませんので、


li {
    animation: base 4s linear 0s alternate infinite;
}

のような書き方で済ませても問題ありません。

発話中のアバターのアニメーションを設定


.Voice_voiceState__xxxxx [class*="Speaking"] + [class*="Voice_user"] [class*="Voice_name"]::after{
    animation: mouth 0.15s steps(2) 0s alternate infinite;
}
2023年1月28日追記:
掲載しているコードの口パク部分のセレクタが一部抜けてしまっていたので、修正しました。


.Voice_voiceState__xxxxx [class*=”Speaking”] + [class*=”Voice_name”]::after


.Voice_voiceState__xxxxx [class*=”Speaking”] + [class*=”Voice_user”] [class*=”Voice_name”]::after

動かないよー!ってなってしまった方、すみませんでした!

Discordで発話中のステータス設定は、相変わらずimg要素が起点になっています。
Speakingを含むclassがついているimg要素を属性セレクタで指定します。

ところで、最新のChromeでは少し前に、とうとうhas擬似クラスが使えるようになりました!

というわけでこんな書き方もできちゃう!?


.Voice_voiceState__OCoZh:has([class*="Speaking"]) [class*="Voice_name"]::after{
    animation: mouth 0.15s steps(2) 0s alternate infinite;
}

とワクワクして試してみたのですが、OBSでは動きませんでした。
Chromiumベースだからいけるかと思ったのに…
OBSのバージョン上げたらいけるかな?

コードのご利用に関して

紹介しているコードは、個人でお楽しみの場合は自由にご利用いただいてかまいません。
法人のご利用および、動画配信プラットフォームその他の収益化しているコンテンツでこの内容を転載される場合には事前にご一報ください。

その他のメッセージ

過去のDiscord Streamkit Overlay カスタムCSSの関連記事