2019-04-15に投稿

nendの広告を表示するVueコンポーネント

nendの広告をVueコンポーネントとして作成し、Nuxt.js上で表示してみました。AdSenseの審査が通らない間使っていたのですが、通ったので必要なくなりました。そのためせっかくなので晒しておきます。

ただ、nend広告の貼り付けタグは癖があるので結構めちゃくちゃなことをやっています。場合によっては怒られる可能性もあるのでご注意を。

そもそもSPAでは使えない

nend広告の貼り付けタグはそもそもSPAでは使えません。

scriptタグで構成されている

全てscriptタグで構成されています。こんな感じです。

<script type="text/javascript">
var nend_params = {"media":メディアID,"site":サイトID,"spot":位置,…};
</script>
<script type="text/javascript" src="メインスクリプト"></script>

Vueコンポーネント上にscriptタグを貼っても基本的に無視されてしまいますので動きません。ただ、JavaScriptのcreateElementでスクリプトタグを作って配置することで動作させる事は可能ですので今回もそのようにしました。具体的にはこんな感じです。

    const script = document.createElement('script')
    script.src = 'URL'
    const container = <Element>this.$refs.container
    container.appendChild(script)

nend_paramsが無いと怒られる

そう、見れば分かるのですがnend_paramsがグローバルに配置されています。そのため、Vueコンポーネント内に隠蔽ができません…。

window.nend_params に入れ、その直後にscriptタグを配置する、という連動するのかどうかも良くわからない流れでやらなければいけなくなっています。

document.writeできないと怒られる

そう、一番の問題はこれでした。document.writeを使っているのですが、Vueコンポーネントはdocumentとは全く関係ない場所で動いているので、そもそもdocument.writeが不可能です。nend広告はSPAを完全に切っていて時代錯誤すぎます。

仕方ないのでdocument.writeを一旦置き換え、終わったらもとに戻す、という方法をとりました。

    const write = window.document.write
    window.document.write = str => {
      container.innerHTML += str
    }
    container.appendChild(script)

ここまでしてようやく表示することができました。もう正直この時点で正式な方法で貼っているとは思えない状態です。

ひとつしか配置できない

前述の方法で表示できるようにはなったのですが、そもそもscriptタグの配置で実行しているので、いつ処理が完了しているのか誰にも分かりません。そのため、1ページに二つ以上広告を配置すると1つ目の処理が終わってない状態で二つ目の処理が動いてしまい、どこのinnerHTMLに値が追加されていくのか全くわからないカオスな状態となります。

キューを使った

とにかく、ひとつずつ処理をしなければならないのでキュー方式で動かすことにしました。

  1. 他のキューがあったら待つ
  2. 配置する
  3. 読み込みが完了するまで待つ

という流れです。コールバックは当然無いので、containerの中にimgタグが現れるまで待つ、という手法を取りました。最大5秒にしていますがwhile & await setTimeoutしてるのでどんな感じで処理が積まれていっているのか謎です。

同じ広告を配置できない

同じ広告を配置できないことが分かりました。本番であればまあ広告枠を作ればいいので問題ないのですが、テスト広告も同じものを配置できないため、1ページ内に複数の広告枠がある場合は仕方がないので大きさも無視して別のものを表示していかなければなりません。

当然、1ページ内にたくさん広告がある場合はテスト表示できません。

成果物

下記が全容になります。ぐちゃぐちゃですので参考にはしないでください。

<template>
  <span ref="container"></span>
</template>

<script lang="ts">
import { Vue, Component, Prop } from 'nuxt-property-decorator'
import { waitFor } from '../client/lib/wait'

declare var window: any

const testSpots = [111111, 222222, 333333, 444444]

function pushQueue(component, time) {
  if (window.nendAdComponents === undefined) {
    window.nendAdComponents = []
  }
  window.nendAdComponents = window.nendAdComponents.filter(
    row => row.location === location.href
  )
  window.nendAdComponents.push({
    component,
    finished: false,
    location: location.href,
    time
  })
}

function isCurrentQueue(component) {
  const current = window.nendAdComponents.find(row => !row.finished)
  if (!current) {
    return false
  }
  return current.component === component
}

function getQueueNo(component) {
  for (let i = 0; i < window.nendAdComponents.length; i++) {
    if (window.nendAdComponents[i].component === component) {
      return i
    }
  }
  return 0
}

function isQueueEnabled(component, time) {
  for (let i = 0; i < window.nendAdComponents.length; i++) {
    const row = window.nendAdComponents[i]
    if (row.component === component && row.time === time) {
      return true
    }
  }
  return false
}

function finishQueue(component) {
  const current = window.nendAdComponents.find(
    row => row.component === component
  )
  if (current) {
    current.finished = true
  }
}

@Component
export default class Nend extends Vue {
  @Prop(String)
  spot: string

  adHtml = ''

  async mounted() {
    const time = Date.now()
    pushQueue(this, time)

    await waitFor(() => isCurrentQueue(this), 5000)
    if (!isQueueEnabled(this, time)) {
      return
    }

    this.adHtml = ''
    window.nend_params = {
      media: process.env.NEND_MEDIA,
      site: process.env.NEND_SITE,
      spot: process.env.NEND_SPOT === '' ? this.spot : process.env.NEND_SPOT,
      type: 1,
      oriented: 1
    }
    if (window.nend_params.media == '11') {
      const queueNo = getQueueNo(this)
      window.nend_params.spot = testSpots[queueNo]
    }
    const script = document.createElement('script')
    script.src = 'メインスクリプトURL'
    const container = <Element>this.$refs.container
    const write = window.document.write
    window.document.write = str => {
      container.innerHTML += str
    }
    container.appendChild(script)

    await waitFor(() => {
      const img = container.querySelector('img')
      return img !== undefined && img !== null
    }, 5000)

    window.document.write = write
    finishQueue(this)
  }
}
</script>

waitForはこんなのです。

export async function waitFor(
  conditionFunc: (() => boolean),
  maxMilliSeconds: number
) {
  let waited = 0
  while (!conditionFunc() && waited < maxMilliSeconds) {
    await wait(1000)
    waited += 1000
  }
}

export function wait(milliSeconds: number) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, milliSeconds)
  })
}

まとめ

こんな感じでとりあえず表示することはできましたが常時心配で仕方がないです。ページ切り替えの際に問題が出るパターンなどもありそうですし。(まあ広告なのでそんな厳密に動かなくても良いのですが)

とりあえずはやく設置タグを変えたほうがいいように思いますね。せめて関数呼び出し方式にしてくれないと厳しい…。


だら@Crieit開発者

Crieitの開発者です。 主にLAMPで開発しているWebエンジニアです(在宅)。大体10年程。 記事でわかりにくいところがあればDMで質問していただくか、案件発注してください。 業務依頼、同業種の方からのコンタクトなどお気軽にご連絡ください。 業務経験有:PHP, MySQL, Laravel5, CakePHP3, JavaScript, RoR 趣味:Elixir, Phoenix, Node, Nuxt, Express, Vue等色々

Crieitは個人で開発中です。 興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!

ボードとは?

関連記事

コメント