PageSourceSearch

https://www.mbc.co.jp/weather/js/weather-widget.js

js mbc.co.jp collected 2026-10-02 05:10:05 UTC 15,070 bytes, 281 lines download raw bytes

1(function() {
2    const imgDirectory = "/weather/icon/";
3    //const jsonDirectory = "/wthr/data/";
4    const jsonDirectory = "/weather/";
5    const localkey = "wthr_myarea_mbc";
6
7    let weatherData = [];
8    let currentIndex = 0;
9    let autoPlayTimer;
10    const intervalTime = 5000; // 自動切り替えの間隔(ミリ秒)
11    
12    const areaArray = {
13       "46201" : {"id" : "46201", "cityCode" : "462012", "cityName" : "鹿児島市"},
14       "46203" : {"id" : "46203", "cityCode" : "462039", "cityName" : "鹿屋市"},
15       "46204" : {"id" : "46204", "cityCode" : "462047", "cityName" : "枕崎市"},
16       "46206" : {"id" : "46206", "cityCode" : "462063", "cityName" : "阿久根市"},
17       "46208" : {"id" : "46208", "cityCode" : "462080", "cityName" : "出水市"},
18       "46210" : {"id" : "46210", "cityCode" : "462101", "cityName" : "指宿市"},
19       "46213" : {"id" : "46213", "cityCode" : "462136", "cityName" : "西之表市"},
20       "46214" : {"id" : "46214", "cityCode" : "462144", "cityName" : "垂水市"},
21       "46215" : {"id" : "46215", "cityCode" : "462152", "cityName" : "薩摩川内市"},
22       "46299" : {"id" : "46299", "cityCode" : "462152", "cityName" : "甑島"},
23       "46216" : {"id" : "46216", "cityCode" : "462161", "cityName" : "日置市"},
24       "46217" : {"id" : "46217", "cityCode" : "462179", "cityName" : "曽於市"},
25       "46218" : {"id" : "46218", "cityCode" : "462187", "cityName" : "霧島市"},
26       "46219" : {"id" : "46219", "cityCode" : "462195", "cityName" : "いちき串木野市"},
27       "46220" : {"id" : "46220", "cityCode" : "462209", "cityName" : "南さつま市"},
28       "46221" : {"id" : "46221", "cityCode" : "462217", "cityName" : "志布志市"},
29       "46222" : {"id" : "46222", "cityCode" : "462225", "cityName" : "奄美市"},
30       "46223" : {"id" : "46223", "cityCode" : "462233", "cityName" : "南九州市"},
31       "46224" : {"id" : "46224", "cityCode" : "462241", "cityName" : "伊佐市"},
32       "46225" : {"id" : "46225", "cityCode" : "462250", "cityName" : "姶良市"},
33       "46303" : {"id" : "46303", "cityCode" : "463035", "cityName" : "三島村"},
34       "46304" : {"id" : "46304", "cityCode" : "463043", "cityName" : "十島村"},
35       "46392" : {"id" : "46392", "cityCode" : "463922", "cityName" : "さつま町"},
36       "46404" : {"id" : "46404", "cityCode" : "464040", "cityName" : "長島町"},
37       "46452" : {"id" : "46452", "cityCode" : "464520", "cityName" : "湧水町"},
38       "46468" : {"id" : "46468", "cityCode" : "464686", "cityName" : "大崎町"},
39       "46482" : {"id" : "46482", "cityCode" : "464821", "cityName" : "東串良町"},
40       "46490" : {"id" : "46490", "cityCode" : "464902", "cityName" : "錦江町"},
41       "46491" : {"id" : "46491", "cityCode" : "464911", "cityName" : "南大隅町"},
42       "46492" : {"id" : "46492", "cityCode" : "464929", "cityName" : "肝付町"},
43       "46501" : {"id" : "46501", "cityCode" : "465011", "cityName" : "中種子町"},
44       "46502" : {"id" : "46502", "cityCode" : "465020", "cityName" : "南種子町"},
45       "46505" : {"id" : "46505", "cityCode" : "465054", "cityName" : "屋久島町"},
46       "46523" : {"id" : "46523", "cityCode" : "465232", "cityName" : "大和村"},
47       "46524" : {"id" : "46524", "cityCode" : "465241", "cityName" : "宇検村"},
48       "46525" : {"id" : "46525", "cityCode" : "465259", "cityName" : "瀬戸内町"},
49       "46527" : {"id" : "46527", "cityCode" : "465275", "cityName" : "龍郷町"},
50       "46529" : {"id" : "46529", "cityCode" : "465291", "cityName" : "喜界町"},
51       "46530" : {"id" : "46530", "cityCode" : "465305", "cityName" : "徳之島町"},
52       "46531" : {"id" : "46531", "cityCode" : "465313", "cityName" : "天城町"},
53       "46532" : {"id" : "46532", "cityCode" : "465321", "cityName" : "伊仙町"},
54       "46533" : {"id" : "46533", "cityCode" : "465330", "cityName" : "和泊町"},
55       "46534" : {"id" : "46534", "cityCode" : "465348", "cityName" : "知名町"},
56       "46535" : {"id" : "46535", "cityCode" : "465356", "cityName" : "与論町"}
57    };
58
59    // 1. ウィジェットの初期化(ここがスッキリします)
60    function initWidget() {
61        const container = document.getElementById('weather-widget-container');
62        if (!container) return;
63
64        buildWidget(container);      // HTML/CSSの構築
65        setupEvents();               // イベントリスナーの登録
66        fetchAndRenderData();        // データ取得と初回描画 ★ここを共通化
67    }
68
69    // --- HTML/CSSの構築処理 ---
70    function buildWidget(container) {
71        const style = document.createElement('style');
72        style.textContent = `
73            .ww-wrapper { box-sizing: border-box; border: 1px solid #ddd; padding: 10px 15px; width: 100%; min-width: 300px; max-width: 400px; border-radius: 8px; font-family: sans-serif; background: #fff; box-shadow: 0 2px 5px rgba(0,0,0,0.05); margin-top: 5px; }
74            .ww-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; color: #2c5282; font-weight: bold; font-size: 14px; }
75            .ww-controls { display: flex; gap: 4px; }
76            .ww-controls button { background: none; border: 1px solid #ccc; border-radius: 4px; cursor: pointer; padding: 2px 8px; font-size: 12px; color: #666; transition: 0.2s; }
77            .ww-controls button:hover { background: #f0f0f0; }
78            .ww-main { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
79            .ww-anim { transition: opacity 0.4s ease; opacity: 1; }
80            .ww-anim.fade-out { opacity: 0; }
81            .ww-content { display: flex; align-items: center; gap: 10px; flex-grow: 1; }
82            .ww-city { font-weight: bold; font-size: 16px; width: 7.5em; display: inline-block; text-align: left; white-space: nowrap; flex-shrink: 0; }
83            .ww-icon { font-size: 28px; line-height: 1; }
84            .ww-icon img { width: 50px; height: auto; vertical-align: middle; } /* 画像アイコン用の調整 */
85            .ww-temps { font-size: 16px; font-weight: bold; white-space: nowrap; }
86            .ww-max { color: #e53e3e; }
87            /*.ww-min { color: #3182ce; font-weight: normal; font-size: 14px;}*/
88            .ww-prob { color: #3182ce;}
89            .ww-divider { color: #cbd5e0; margin: 0 4px; font-weight: normal; }
90            .ww-detail-btn { background: #edf2f7; border: none; border-radius: 4px; padding: 4px 10px; font-size: 12px; color: #4a5568; text-decoration: none; cursor: pointer; transition: 0.2s; white-space: nowrap; flex-shrink: 0; }
91            .ww-detail-btn:hover { background: #e2e8f0; }
92            @media (max-width: 380px) {
93                .ww-wrapper { padding: 8px 10px; } /* 左右の余白を少し減らす */
94                .ww-main { gap: 5px; } /* 詳細ボタンとの隙間を詰める */
95                .ww-content { gap: 5px; } /* 各要素(市町村・アイコン・温度)の隙間を詰める */
96                .ww-city { font-size: 13px; width: 7.2em; } /* 「いちき串木野市(7文字)」が入るギリギリの幅 */
97                .ww-icon img { width: 38px; } /* アイコンサイズを少し縮小 */
98                .ww-temps { font-size: 13px; }
99                .ww-prob { font-size: 13px; }
100                .ww-detail-btn { padding: 4px 6px; font-size: 11px; }
101            }
102        `;
103        document.head.appendChild(style);
104
105        container.innerHTML = `
106            <div class="ww-wrapper">
107                <div class="ww-header">
108                    <span id="ww-title" class="ww-anim">読み込み中...</span>
109                    <div class="ww-controls">
110                        <button id="ww-update-btn" title="更新">↻</button>
111                        <button id="ww-prev-btn" title="前へ">&lt;</button>
112                        <button id="ww-next-btn" title="次へ">&gt;</button>
113                    </div>
114                </div>
115                <div class="ww-main">
116                    <div id="ww-display-area" class="ww-content ww-anim">
117                        </div>
118                    <a href="#" id="ww-detail-link" class="ww-detail-btn">詳細 &gt;</a>
119                </div>
120            </div>
121        `;
122    }
123
124    // --- イベントリスナーの登録処理 ---
125    function setupEvents() {
126        // 更新ボタン:共通化したデータ取得関数を呼ぶだけ
127        document.getElementById('ww-update-btn').addEventListener('click', () => {
128            fetchAndRenderData(); 
129        });
130
131        document.getElementById('ww-prev-btn').addEventListener('click', () => changeData(-1));
132        document.getElementById('ww-next-btn').addEventListener('click', () => changeData(1));
133    }
134
135    // --- 【共通化】データ取得・フィルタリング・描画の実行 ---
136    function fetchAndRenderData() {
137        //const url = jsonDirectory + "kagoshima.json?nocache=" + new Date().getTime();
138        const url = jsonDirectory + "get_weather_json.php";
139        
140        $.getJSON(url, function(data) {
141            const myAreas = getMyAreas();
142            
143            // データをフィルタリングしてグローバル変数にセット
144            //weatherData = data.body.forecast.filter(forecastItem => {
145            //    const areaCode = forecastItem.area.code;
146            //    return myAreas.some(codeA => areaCode.startsWith(codeA));
147            //});
148            // myAreas(設定した地域)の順番に合わせてデータを並べて格納する
149            weatherData = [];
150            
151            myAreas.forEach(codeA => {
152                // その地域コード(codeA)に合致するデータをJSONの中から探す
153                const matchedItems = data.body.forecast.filter(forecastItem => 
154                    forecastItem.area.code.startsWith(codeA)
155                );
156                
157                // 見つかったデータを weatherData に順番に追加していく
158                matchedItems.forEach(item => {
159                    // 同じデータが重複して追加されるのを防ぐ
160                    if (!weatherData.includes(item)) {
161                        weatherData.push(item);
162                    }
163                });
164            });
165
166            // 必要であればインデックスを0(最初の地域)に戻す
167            // currentIndex = 0;
168
169            // 描画してタイマーをリセット
170            renderData();
171            //resetAutoPlay();
172
173        }).fail(function() {
174            console.log("データの取得に失敗しました。[" + url + "]");
175            document.getElementById('ww-title').textContent = "取得エラー";
176        });
177    }
178
179    // クッキーやローカルストレージからエリアを取得
180    function getMyAreas() {
181        let cookid = typeof Cookies !== 'undefined' ? Cookies.get('cookid') : null;
182        
183        if(!areaArray[cookid]) { // ※areaArrayは外部で定義されている想定
184            // getParam関数も外部で定義されている想定
185            cookid = typeof getParam === 'function' ? getParam('area') : null; 
186        }
187        if(!cookid || !areaArray[cookid]) {
188            cookid = "46201"; // 鹿児島市
189        }
190        
191        if (!localStorage.getItem(localkey)) {
192            localStorage.setItem(localkey, JSON.stringify({ areacd: [] }));
193        }
194        
195        let localSData = JSON.parse(localStorage.getItem(localkey));
196        if(localSData === undefined || localSData.areacd === undefined || localSData.areacd === "") {
197            localSData = { areacd: [] };
198            localSData.areacd.push(cookid);
199        }
200        
201        localSData.areacd = localSData.areacd.filter(item => item !== cookid);
202        localSData.areacd.unshift(cookid);
203        
204        return localSData.areacd;
205    }
206
207    // 画面にデータを反映させる関数
208    function renderData() {
209        if (!weatherData || weatherData.length === 0) return; // データが空の場合は処理しない
210
211        const displayArea = document.getElementById('ww-display-area');
212        const titleArea = document.getElementById('ww-title');
213        const detailLink = document.getElementById('ww-detail-link');
214        const data = weatherData[currentIndex];
215
216        displayArea.classList.add('fade-out');
217        titleArea.classList.add('fade-out');
218        
219        const now = new Date();
220        const currentDay = now.getDate();
221        const currentHourStr = now.toLocaleTimeString('ja-JP', { hour: '2-digit' });
222        const dayOfWeeks = ["日", "月", "火", "水", "木", "金", "土"];
223        
224        // 直近の未来の時間を探す
225        for(const hour of data.item.hourly) {
226            const dateObj = new Date(hour.date);
227            const day = dateObj.getDate();
228            const time = dateObj.toLocaleTimeString('ja-JP', { hour: '2-digit' });
229            
230            // 過去の時間はスキップ
231            if (day === currentDay && time < currentHourStr) {
232                continue;
233            }
234            
235            //setTimeout(() => {
236                titleArea.textContent = time + "の天気";
237                
238                displayArea.innerHTML = `
239                    <div class="ww-city">${data.area.cityName}</div>
240                    <div class="ww-icon"><img src="${imgDirectory}${hour.icon}" alt="${hour.dispWeather}"></div>
241                    <div class="ww-temps">
242                        <span class="ww-max">${hour?.temp == null ? '-' : hour.temp}℃</span><span class="ww-divider">|</span><span class="ww-prob">${hour.rainProb}%</span>
243                    </div>
244                `;
245                detailLink.href = "/weather/";
246                
247                displayArea.classList.remove('fade-out');
248                titleArea.classList.remove('fade-out');
249            //}, 400);
250            
251            break; // 1件目(最新の未来データ)を処理したらループを抜ける
252        }
253    }
254
255    // データ切り替え処理
256    function changeData(direction) {
257        if (weatherData.length === 0) return;
258        currentIndex = (currentIndex + direction + weatherData.length) % weatherData.length;
259        renderData();
260        //resetAutoPlay();
261    }
262
263    // 自動再生の制御
264    function startAutoPlay() {
265        autoPlayTimer = setInterval(() => {
266            changeData(1);
267        }, intervalTime);
268    }
269
270    function resetAutoPlay() {
271        clearInterval(autoPlayTimer);
272        startAutoPlay();
273    }
274
275    // 起動処理
276    if (document.readyState === 'loading') {
277        document.addEventListener('DOMContentLoaded', initWidget);
278    } else {
279        initWidget();
280    }
281})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.