1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

任意の文字列を円周上に埋め尽くしたい

Last updated at Posted at 2019-05-05

要はRed Hot Chili Peppersのロゴのテキストみたいなやつを同じテキストをリピートさせてやりたい。

探してみたところこのあたりが引っかかった:

なるほど。どうにもシンプルにやる方法はなさそう。

しかし、SVGのtext要素はPathに沿って並べることができることはわかった。
https://developer.mozilla.org/ja/docs/Web/SVG/Element/text

SVGは現状だいたいのブラウザで対応しているっぽいしいけそう。
https://caniuse.com/#search=svg

円を描くPathを動的に生成できれば楽に自動化できそうだ。すでに書いているひとがいた。
https://tyru.github.io/svg-circle-misc-algorithm/

拝借してゴニョったところできた。やったね。

<template>
  <svg
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink"
    :viewBox="viewBox"
    :style="{ maxWidth: boxSize + 'px' }"
  >
    <title v-text="text" />
    <defs>
      <path
        id="textcircle"
        :d="circlePath"
      />
    </defs>
    <text :font-size="fontSize">
      <textPath xlink:href="#textcircle" v-text="repeatedAroundText" />
    </text>
  </svg>
</template>

<script>
/*
 * thanks: https://tyru.github.io/svg-circle-misc-algorithm/
 */

const SEGMENTS = 8
const ANGLE = 2 * Math.PI / SEGMENTS
const HALF_ANGLE_RATIO = Math.tan(ANGLE / 2)

function range(from, to) {
  const length = to - from
  return Array.from({ length: length + 1 }, (v, k) => k + from)
}

function drawCirclePath(center, radius) {
  const anchorX = theta => radius * Math.cos(theta)
  const anchorY = theta => radius * Math.sin(theta)
  const controlX = theta => anchorX(theta) + radius * HALF_ANGLE_RATIO * Math.cos(theta - Math.PI / 2)
  const controlY = theta => anchorY(theta) + radius * HALF_ANGLE_RATIO * Math.sin(theta - Math.PI / 2)
  return [
    ['M', center + radius, center],
    ...range(1, SEGMENTS).map((index) => {
      const theta = index * ANGLE
      return ['Q', controlX(theta) + center, controlY(theta) + center, anchorX(theta) + center, anchorY(theta) + center]
    })
  ]
}

export default {
  props: {
    text: {
      type: String,
      default: ''
    },
    fontSize: {
      type: Number,
      default: 12
    },
    boxSize: {
      type: Number,
      default: 800
    }
  },
  data() {
    return {}
  },
  computed: {
    centerPosition() {
      return Math.floor(this.boxSize / 2)
    },
    radius() {
      return Math.floor(this.boxSize / 2) - this.fontSize * 2
    },
    viewBox() {
      return [0, 0, this.boxSize, this.boxSize].join(' ')
    },
    repeatedAroundText() {
      const repeatCount = Math.ceil((this.radius * 2 * Math.PI) / this.fontSize / this.text.length)
      return this.text.repeat(repeatCount)
    },
    circlePath() {
      const circlePath = drawCirclePath(this.centerPosition, this.radius)
      return circlePath.map(path => path[0] + path.slice(1).join(',')).join(' ')
    }
  }
}
</script>

SVGそのものの描画コストはSVGのソースも小さいし問題ないと信じてる。
このへんのDOM生成が重くなってもNuxt.jsでSSRしてやったりすればよさそう。

ちょっと気になる記事があったけど、等幅配置の際の問題のようなのでこれには影響なさそう。
https://qiita.com/MKLemma/items/c38cd2587a1451a95e3e

もちろん、別のブラウザ依存の問題がないとも言えないので、その後は要調査。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?