Edited at

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

要は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

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