Vue.createApp({ data() { return { dataUrl: "data/cmList.json", awards: [], others: [], slideChunkSize: 17, // ▼ 追加:シャッフル後のソースを保持 shuffledSource: [], // ▼ 追加:スライダー設定(infiniteslideのオプション相当) slider: { speed: 75, // 小さいほど速い…ではないので後述CSSで調整 direction: "left", // "left" or "right" pauseOnHover: false, // falseなら止めない }, }; }, computed: { // 元データ:受賞→その他(見栄え優先) slideSourceRaw() { const a = Array.isArray(this.awards) ? this.awards : []; const o = Array.isArray(this.others) ? this.others : []; return a.concat(o); }, // シャッフル済み(描画の基準はこれ) slideSource() { return this.shuffledSource.length ? this.shuffledSource : this.slideSourceRaw; }, slide01() { return this.slideSource.slice(0, this.slideChunkSize); }, slide02() { return this.slideSource.slice( this.slideChunkSize, this.slideChunkSize * 2 ); }, // ▼ 追加:無限ループ用に2周分を返す(DOM複製ではなく“データを2回描画”) loopedSlide01() { return this.slide01.concat(this.slide01); }, loopedSlide02() { return this.slide02.concat(this.slide02); }, // ▼ 追加:CSS変数に渡す用 slideVars() { // infiniteslideのspeed=75 を「秒」に読み替える(好みで調整OK) // 例: 75 -> 30s くらいの体感に寄せる const durationSec = Math.max(10, Math.round(this.slider.speed * 0.4)); // 75 => 30秒 return { "--marquee-duration": `${durationSec}s`, "--marquee-direction": this.slider.direction === "right" ? "reverse" : "normal", }; }, }, methods: { async loadJson() { const res = await fetch(this.dataUrl, { cache: "no-store" }); if (!res.ok) { console.error("JSONの読み込みに失敗:", res.status, this.dataUrl); return; } const json = await res.json(); this.awards = Array.isArray(json.awards) ? json.awards : []; this.others = Array.isArray(json.others) ? json.others : []; }, // ▼ 追加:Fisher–Yates shuffle(jQuery版shuffleContentの置き換え) shuffleArray(arr) { const a = arr.slice(); for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; }, // ▼ 追加:初期化(データ確定後に1回だけシャッフル) initSlides() { this.shuffledSource = this.shuffleArray(this.slideSourceRaw); }, }, async mounted() { await this.loadJson(); this.initSlides(); // ← ここで1回だけシャッフル await this.$nextTick(); }, components: { "cm-slide": { props: ["item"], computed: { thumbUrl() { return `https://i.ytimg.com/vi/${this.item.videoId}/hqdefault.jpg`; }, watchUrl() { return `https://www.youtube.com/watch?v=${this.item.videoId}`; }, altText() { if (this.item.awardLabel) { return `【${this.item.awardLabel}】${this.item.jichitai} ${this.item.title}`; } return `${this.item.jichitai} ${this.item.title}`; }, }, template: `
  • `, }, }, }).mount("#cmSlideApp");