This page looks best with JavaScript enabled

【Vuetify】埋め込みツイート風コンポーネント

 ·   ·  ☕ 4 分で読めます
✏️

はじめに

埋め込みツイートの表示をするにあたり、公式の埋め込みツイートを使おうと思ったのですが、なぜかVueで<script>が動きませんでした。原因を調べてもいまいちわからなかったので、自作したほうが早いのではないかと思いました。
一応、Vuetifyにも「Twitter card」というものはあったのですが……なんか違う。

image.png

というわけで、v-cardを基本にそれっぽいレイアウトにしてみました。

つくるもの

こんな感じのツイート風コンポーネントです。
tweet

See the Pen Oembed Tweet by END (@aiandrox) on CodePen.

環境

  • Vue.js 2.6.11
  • Vuetify 2.2.21
  • Material Design Icon 5.1.45

アイコンは別のものでも代用可能だと思います。Twitterアイコン、リプライ、リツイート、いいねで使用しています。

使用データ

サンプルコードでは以下のようにデータをネストしています。

  • tweet
    • tweetId(ツイートのID)
    • html(本文部分のHTML)
    • tweetedAt(ツイート日時)
    • user
      • name(表示名)
      • screenName(@screen_nameのユーザー名)
      • avatarUrl(アイコンの画像URL)

htmlはGET statuses/oembedで取得した埋め込みツイート用のHTMLから<p>タグの内側を抜き出して利用しました。
日時はDay.jsなどを使ってフォーマットを整えることをおすすめします。

ツイート表示の規定

Twitterの埋め込みツイートには表示の規定があるので、それに従ってカードを作成します。
こちらのDisplay requirements – Twitter DevelopersをざっくりGoogle意訳しました。間違っていたらご指摘をお願いします。

  • 実際のアカウントによる変更を加えられていないツイートを表示すること。
  • 公式ツイッターロゴを表示すること。
    • 個々のツイートの右上隅に表示するか、タイムラインに直接添付すること。
    • ロゴは、画像と同じ高さにすること。
  • 作成者のプロフィール画像、@ユーザーネーム、表示名を常に表示し、Twitterプロフィールページにリンクさせること。
  • @ユーザーネームは@を用いて表示すること。
  • プロフィール画像は、表示名と@ユーザーネームの左側に配置すること(ただし右から左に読む言語のツイートは例外)
  • ツイートのテキストは作成者の表示名と@ユーザーネームの下の行に表示すること。
  • ツイートのテキストと作成者の周りのスペースはツイートのパーマリンクにリンクさせること。
  • テキスト内のツイートエンティティは、Twitter上の適切なホームに適切にリンクすること。
    • @ユーザーネームによるメンションは言及されたユーザーのプロフィールページにリンクすること。
    • ハッシュタグは、ハッシュタグをクエリとしてTwitter検索にリンクすること。
    • テキスト内のリンクは、URL entities APIにおけるdisplay_urlを表示し、元のt.co urlにリンクすること(詳細はt.co best practices articleを参照)
  • ツイートのタイムスタンプを表示し、ツイートのパーマリンクにリンクすること。
  • プラットフォームに存在しないツイートのモックアップを使用しないこと。

コード

CodePenの方はそれだけで完結するようにしています。
こちらのコードはコンポーネント用のコードです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<template>
  <div>
    <v-card flat outlined max-width="500" class="mt-3" :href="tweetUrl">
      <v-card-title>
        <v-list-item class="pl-0">
          <v-list-item :href="userUrl">
            <v-list-item-avatar color="grey" size="40">
              <v-img :src="tweet.user.avatarUrl" />
            </v-list-item-avatar>
            <v-list-item-content>
              <v-list-item-title>{{ tweet.user.name }}</v-list-item-title>
              <v-list-item-subtitle class="font-weight-light">@{{ tweet.user.screenName }}</v-list-item-subtitle>
            </v-list-item-content>
          </v-list-item>
          <v-spacer />
          <v-list-item-action>
            <v-icon color="blue">mdi-twitter</v-icon>
          </v-list-item-action>
        </v-list-item>
      </v-card-title>

      <v-card-text class="text--primary" v-html="tweet.html" />
        <v-card-actions>
          <v-btn
            v-for="button in buttons"
            :key="button.icon"
            :href="button.url"
            :color="button.color"
            icon
          >
            <v-icon>{{ button.icon }}</v-icon>
          </v-btn>
          <v-spacer />
          <span class="body-2 font-weight-light">{{ tweet.tweetedAt }}</span>
        </v-card-actions>
      </v-card>
    </v-card>
  </div>
</template>

<script>
export default {
  data() {
    return {
      tweet: {
        tweetId: "0000000000",
        html: "テキスト<br>改行するとこんな感じ",
        tweetedAt: "2020-12-31 10:51",
        user: {
          name: "ユーザー",
          screenName: "screen_name",
          avatarUrl: "https://avataaars.io/?avatarStyle=Transparent&topType=ShortHairShortCurly&accessoriesType=Prescription02&hairColor=Black&facialHairType=Blank&clotheType=Hoodie&clotheColor=White&eyeType=Default&eyebrowType=DefaultNatural&mouthType=Default&skinColor=Light"
        },
      }
    }
  },
  computed: {
    userUrl() {
      return `https://twitter.com/${this.tweet.user.screenName}`
    },
    tweetUrl() {
      return `https://twitter.com/${this.tweet.user.screenName}/status/${this.tweet.tweetId}`
    },
    replyUrl() {
      return `https://twitter.com/intent/tweet?in_reply_to=${this.tweet.tweetId}`
    },
    retweetUrl() {
      return `https://twitter.com/intent/retweet?tweet_id=${this.tweet.tweetId}`
    },
    likeUrl() {
      return `https://twitter.com/intent/like?tweet_id=${this.tweet.tweetId}`
    },
    buttons() {
      return [
        { url: this.replyUrl, color: "gray", icon: "mdi-chat-outline" },
        { url: this.retweetUrl, color: "green", icon: "mdi-twitter-retweet" },
        { url: this.likeUrl, color: "pink", icon: "mdi-heart-outline" }
      ]
    },
  },
}
</script>

おわりに

プロフィールのリンクの範囲が気になりますが、これで妥協点とします。
もっといい感じにできそうなら踏み台にしていただきたいです。

Share on

END
END
@aiandrox

目次