Vue.jsとSVGを用いた3Dタグクラウドの実装

タグクラウドの基本構造

SVG要素とVue.jsのデータバインディングを組み合わせて、インタラクティブな3Dタグクラウドを実装します。各タグは球面上に均等に分布し、Z座標に基づいてフォントサイズと透明度が変化します。

コア実装の概要

  • 球面座標系を用いたタグの初期位置計算
  • requestAnimationFrameによるスムーズな回転アニメーション
  • マウス移動に連動した回転速度の制御
  • Vueのリアクティブシステムを活用した座標更新

実装コード

<div id="tagCloud">
    <svg :width="canvasWidth" :height="canvasHeight" @mousemove="handleMouseMove">
        <a v-for="item in tagItems" :href="item.link" :key="item.id">
            <text 
                :x="item.posX" 
                :y="item.posY" 
                :font-size="computeFontSize(item.depth)" 
                :fill-opacity="computeOpacity(item.depth)">
                {{ item.label }}
            </text>
        </a>
    </svg>
</div>

Vueインスタンスの設定

new Vue({
    el: '#tagCloud',
    data: {
        canvasWidth: 700,
        canvasHeight: 700,
        tagCount: 20,
        sphereRadius: 200,
        rotationRateX: Math.PI / 360,
        rotationRateY: Math.PI / 360,
        tagItems: []
    },
    computed: {
        centerX() { return this.canvasWidth / 2; },
        centerY() { return this.canvasHeight / 2; }
    },
    created() {
        this.initializeTags();
    },
    mounted() {
        this.startRotation();
    },
    methods: {
        initializeTags() {
            const tags = [];
            for (let i = 0; i < this.tagCount; i++) {
                const phi = Math.acos(-1 + (2 * i + 1) / this.tagCount);
                const theta = phi * Math.sqrt(this.tagCount * Math.PI);
                
                tags.push({
                    id: i,
                    label: `Tag${i}`,
                    posX: this.centerX + this.sphereRadius * Math.sin(phi) * Math.cos(theta),
                    posY: this.centerY + this.sphereRadius * Math.sin(phi) * Math.sin(theta),
                    depth: this.sphereRadius * Math.cos(phi),
                    link: 'https://example.com/tag' + i
                });
            }
            this.tagItems = tags;
        },
        
        rotateAroundX(angle) {
            const cos = Math.cos(angle);
            const sin = Math.sin(angle);
            
            this.tagItems.forEach(tag => {
                const relY = tag.posY - this.centerY;
                const newY = relY * cos - tag.depth * sin + this.centerY;
                const newZ = tag.depth * cos + relY * sin;
                
                tag.posY = newY;
                tag.depth = newZ;
            });
        },
        
        rotateAroundY(angle) {
            const cos = Math.cos(angle);
            const sin = Math.sin(angle);
            
            this.tagItems.forEach(tag => {
                const relX = tag.posX - this.centerX;
                const newX = relX * cos - tag.depth * sin + this.centerX;
                const newZ = tag.depth * cos + relX * sin;
                
                tag.posX = newX;
                tag.depth = newZ;
            });
        },
        
        computeFontSize(z) {
            return 20 * (600 / (600 - z));
        },
        
        computeOpacity(z) {
            return (400 + z) / 600;
        },
        
        handleMouseMove(event) {
            const mouseX = event.clientX - this.centerX;
            const mouseY = event.clientY - this.centerY;
            
            this.rotationRateX = Math.max(-0.02, Math.min(0.02, mouseX * 0.0001));
            this.rotationRateY = Math.max(-0.02, Math.min(0.02, mouseY * 0.0001));
        },
        
        startRotation() {
            const animate = () => {
                this.rotateAroundX(this.rotationRateX);
                this.rotateAroundY(this.rotationRateY);
                requestAnimationFrame(animate);
            };
            requestAnimationFrame(animate);
        }
    }
});

技術的特徴

この実装では、球面座標系からの変換計算により均等なタグ配置を実現しています。Vueのリアクティブシステムを活用することで、座標更新時のDOM操作を最小限に抑え、スムーズなアニメーションを実現しています。マウスインタラクションにより回転速度を動的に制御できる点も特徴です。

タグ: vue.js SVG javascript 3Dグラフィックス フロントエンド

7月3日 20:29 投稿