├── .gitignore ├── README.md ├── css └── all.css ├── data ├── viewData.json └── viewZip.json ├── index.html └── js ├── all.js └── demo_ga.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript-TravelMap 2 | 3 | ![](http://www.guastudio.com//uploads/content/20170507/travelMap.png) 4 | 5 | 6 | ### [[連結]](https://guahsu.io/JavaScript-TravelMap/index.html) 7 | 8 | 台北市的旅遊景點資料站,資料來源是政府資料開放平臺, 9 | 樣式的部分還是簡單的HTML跟CSS刻,主軸放在Javascript上, 10 | 紀錄這次在JavaScript上遇到跟重新學習理解到的東西。 11 | 12 | --- 13 | ## querySelector & querySelectorAll 14 | 元素選擇器! 15 | 還記得以前最常使用的就是要找ID時用document.getElementById(), 16 | 當要找Class的時候要用getElementsByClassName()。 17 | 18 | 而這次學習了使用`querySelector()`以及`querySelectorAll()`這兩個函式: 19 | 共通點是都可以用css的規則來作為選擇,例如ID用`#`,ClassName用`.`來操作, 20 | 例如: 21 | ```html 22 |
AAA
23 |
BBB
24 |
CCC
25 |
DDD
26 | ``` 27 | 要取得AAA的資料可以透過`querySelector('#divID')` 28 | 要取得BBB的資料可以透過`querySelector('.divClass')` 29 | 要取得DDD的資料可以透過`querySelector('div[data-no="1"]')` 30 | 31 | 但是若有重複的className透過`querySelector('.divClass')`只會取到第一筆`BBB`的值, 32 | 所以就要用`querySelectorAll('.divClass')`來取得所有className是`.divClass`元素, 33 | 但特別要注意的的是,透過`querySelectorAll()`取得的值會被放入陣列中, 34 | 所以例如寫了: 35 | ````javascript 36 | var data = document.querySelectorAll('.divClass').textContent; 37 | ```` 38 | 會得到`['BBB','CCC']`這樣的內容,要取出`BBB`就是`data[0]`! 39 | 40 | 學到這裡時,真心覺得jQuery的`$('')`真的很方便啊XD! 41 | 42 | --- 43 | ## addEventListener 44 | 綁定事件,使用方法是`element.addEventListner(事件, 執行內容, useCapture)` 45 | 46 | **事件** 47 | 指的是要偵測的事件,例如點擊`click`,內容變換`change`、鍵盤動作`keyCode` 48 | 49 | **執行內容** 50 | 指的是要執行的項目,可以直接指定function或直接寫在裡面 51 | 例如: 52 | ````javascript 53 | document.body.addEventListner('click', SomeFunction, true); 54 | document.body.addEventListner('click', function(e) { console.log(e) }, false); 55 | ```` 56 | 要注意的是當使用第一種方法直接呼叫別的function時,不能傳參數, 57 | 若要傳參數只能在包一層function,例如: 58 | ````javascript 59 | document.body.addEventListner('click', function(e) { SomeFunction(param) }, false); 60 | ```` 61 | 62 | **useCapture** 63 | 目前沒有使用情境,老實說並不是很了解的參數, 64 | 但他是一個`true/false`的設定值,預設是`false`。 65 | 當設定為`true`時,會從指定元素的最外層元素開始往內層執行, 66 | 反之設定為`false`則從指定元素開始往外層執行,舉例: 67 | ```html 68 |
69 |
70 |
71 |
72 | ``` 73 | 當設定 74 | ````javascript 75 | document.querySelector('.child').addEventListener('click',function() { console.log('C') }), ture) 76 | document.querySelector('.father').addEventListener('click',function() { console.log('F') }), ture) 77 | ```` 78 | 會先印出F(外層),若設定為false時會先印出C(內層) 79 | 若father設定`false`,child設定成`ture`,則會先印`C`,反之則先印`F` 80 | 81 | --- 82 | ## function(e){} 83 | 這次使用了很多fuction的狀態偵測, 84 | 例如前兩個`querySelectorAll`跟`addEventListner`組合時就會用到: 85 | ```javascript 86 | /** 87 | * 景點資料按鈕功能綁定 88 | */ 89 | function setReadMoreButton() { 90 | var readMoreButton = document.querySelectorAll('.button--readMore'); 91 | for (var i = 0; i < readMoreButton.length; i++) { 92 | readMoreButton[i].addEventListener('click', function(e) { 93 | showViewData(e.srcElement.getAttribute('data-viewNo')); 94 | }, false); 95 | } 96 | } 97 | ``` 98 | 透過`e.srcElement`來取得觸發事件的元素,進一步取得要操作的值。 99 | 100 | --- 101 | ## XMLHttpRequest 102 | 為了要獲取JSON內容來使前端動態組成景點內容, 103 | 使用XHR(XMLHttpRequest)來做: 104 | ```javascript 105 | /** 106 | * 取得台北市行政區資料 107 | * 透過AJAX取得JSON資料 108 | */ 109 | var areaData = ''; //全部的行政區資料(全域變數) 110 | function getAreaData() { 111 | //建立一個新的XMLHttpRequest 112 | var xhr = new XMLHttpRequest(); 113 | //設定這個xhr來get放在data資料夾底下的json檔案 114 | xhr.open('get', 'data/viewZip.json', true); 115 | //傳送這個xhr的請求 116 | xhr.send(null); 117 | //當取得xhr回應後,執行這個function 118 | xhr.onload = function() { 119 | //檢查狀態碼,若非200連線OK則顯示錯誤訊息 120 | if (xhr.status == 200) { 121 | areaData = JSON.parse(xhr.responseText); 122 | createAreaSelect(); //建立行政區下拉選單 123 | getViewData(); //取得景點資料 124 | }else { 125 | document.querySelector('.box-area').innerHTML = '

取得資料時發生錯誤,請檢查網路狀態並重新整理:)

'; 126 | } 127 | } 128 | } 129 | ``` 130 | 131 | --- 132 | ## window.history 133 | AJAX動態組成的資料在使用者眼裡看起來像是換了一頁, 134 | 但實際上網頁本身並沒有切換頁面,只是”替換“了內容, 135 | 所以自然瀏覽器也不會去紀錄上一頁、下一頁的資料, 136 | 導致切了很多內容後,想回到上一頁卻離開了這個網站。 137 | 138 | 為了解決這個問題,就要自己產生紀錄! 139 | 從`window.history`中可以得知每次對頁面的切換都會有紀錄, 140 | 可以透過`history.pushState(object, title, url)`來增加紀錄。 141 | 參數分別是(要寫進紀錄的值、標題、網址),其中第二個參數目前是沒作用的。 142 | 143 | 並可用`window.onpopstate`來偵測瀏覽器的上、下頁行為 144 | 以這個實作的範例來說,我的想法跟流程是這樣: 145 | 146 | 進入網頁時,先寫入當時的行政區與頁碼到history.state中, 147 | 並同時在localStorage寫入相同值,當使用者點擊上、下頁時, 148 | 將寫入的值取出並透過這組值來重組頁面資料,達到切頁的效果。 149 | 150 | localStorage寫入值的用途為當重新整理時,要撈到重整前的值來重組畫面, 151 | 故當程式呼叫到寫入瀏覽紀錄時也要寫、動作切上下頁時也要寫入。 152 | 153 | ```javascript 154 | /** 155 | * 寫入瀏覽紀錄 156 | * @param {*} areaName 行政區 157 | * @param {*} pageNo 頁碼 158 | */ 159 | function setHistory(areaName, pageNo) { 160 | //紀錄當前行政區及頁數,並儲存在localstorage中 161 | //建立紀錄資料變數historyData,並將行政區與頁碼用字串方式存入變數中 162 | var historyData = '{"areaName":"' + areaName + '","pageNo":' + pageNo + '}'; 163 | //將當前紀錄資料存入瀏覽歷史中 164 | history.pushState(historyData, '', ''); 165 | //將當前紀錄資料存入localStorage中,儲存最後一次瀏覽的頁面資訊 166 | localStorage.setItem('lastPage', historyData); 167 | } 168 | ``` 169 | ```javascript 170 | /** 171 | * 處理瀏覽器上下頁行為 172 | */ 173 | window.onpopstate = function(e) { 174 | if (e.state) { 175 | //將當前的頁面存入localStorage中,儲存最後一次瀏覽的頁面資訊 176 | localStorage.setItem('lastPage', e.state); 177 | //解析動作取得的瀏覽紀錄 178 | var historyData = JSON.parse(e.state); 179 | //組出對應的景點資料 180 | setViewData(historyData.areaName, historyData.pageNo); 181 | } else { 182 | //若已經沒有自己寫入的值,就直接進行預設的上一頁動作 183 | history.back(); 184 | } 185 | } 186 | ``` 187 | 188 | --- 189 | ## CSS 190 | 小小紀錄一下這次CSS遇到的狀況: 191 | 192 | **使用BEM命名規則** 193 | 194 | 各種變數跟樣式命名真的是我很弱的一部份, 195 | 除了英文很破沒幾個單字可以拿來命名之外, 196 | 就是沒有一個規範可以去遵循,後來從前端電子報中看到這個BEM這個關鍵字, 197 | 這種命名方式是遵循Black__Element--Modifier這樣的方式去命名的, 198 | 雖然看起來很長很亂,但其實對健忘的我來說很好找到對應元素, 199 | 不過block in block的處理及modifier的定義我沒有很充分理解, 200 | 第一次使用,會再找機會參考別人的做法。 201 | 202 | **select變更樣式** 203 | 204 | HTML的原生select修改樣式成背景透明後,在手機上的小箭頭居然會不見! 205 | `background-color: rgba(255, 255, 255, 0)` 206 | 207 | 原本想說使用圖片來替代,後來因為解析度的關係(箭頭會看起來糊糊的), 208 | 在select旁邊寫了一個svg的小箭頭來換頂替。 209 | ```html 210 | 211 | 212 | 213 | ``` 214 | 且為了安全起見,也還是把select的圖標隱藏了 215 | ` -webkit-appearance: none;` 216 | 然後這個我根本沒有在管瀏覽器兼容性啦XD! 217 | 218 | -------------------------------------------------------------------------------- /css/all.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | position: relative; 4 | box-sizing: border-box; 5 | vertical-align: top; 6 | } 7 | body { 8 | font-family: 微軟正黑體; 9 | color: #202121; 10 | letter-spacing: 1px; 11 | margin: 0px; 12 | background-color: #f4f7f6; 13 | } 14 | .button { 15 | border: none; 16 | display: inline-block; 17 | outline: 0; 18 | padding: 8px 16px; 19 | vertical-align: middle; 20 | overflow: hidden; 21 | text-decoration: none; 22 | cursor: pointer; 23 | user-select: none; 24 | } 25 | .button:hover { 26 | color: #fcfcfc; 27 | background-color: #9fd4cd; 28 | } 29 | .button--readMore { 30 | font-family: 微軟正黑體; 31 | font-size: 14px; 32 | letter-spacing: 1px; 33 | font-weight: 400; 34 | margin-top: 10px; 35 | color: #fff; 36 | background-color: #42ab9e; 37 | padding: 8px 50px; 38 | } 39 | .button--close { 40 | font-size: 20px; 41 | position: absolute; 42 | right: 0; 43 | top: 0; 44 | } 45 | .container { 46 | padding-top: 300px; 47 | margin: auto; 48 | } 49 | .container__header { 50 | top: 0px; 51 | position: absolute; 52 | width: 100%; 53 | height: 300px; 54 | margin: auto; 55 | text-align: center; 56 | border-bottom: solid 1px #dbe0df; 57 | background: linear-gradient(to top, #dbe0df, #42ab9e 100%); 58 | } 59 | .header { 60 | display: inline-block; 61 | border: solid 2px #fff; 62 | padding: 30px 50px; 63 | letter-spacing: 3px; 64 | color: #fff; 65 | top: 50px; 66 | width: 60%; 67 | } 68 | .header__title { 69 | font-size: 30px; 70 | font-weight: 300; 71 | } 72 | .header__desc { 73 | font-size: 24px; 74 | font-weight: 200; 75 | } 76 | 77 | .content { 78 | padding: 0px 20px; 79 | text-align: center; 80 | margin: auto; 81 | } 82 | .content__select { 83 | font-family: 微軟正黑體; 84 | font-weight: 100; 85 | color: #42ab9e; 86 | width: 250px; 87 | top: -2px; 88 | text-align-last: center; 89 | height: 70px; 90 | font-size: 24px; 91 | border-radius: 0px; 92 | border: solid 1px #dbe0df; 93 | border-top: none; 94 | background-color: rgba(255, 255, 255, 0); 95 | -webkit-appearance: none; 96 | margin-bottom: 30px; 97 | } 98 | .content__select:focus { 99 | outline: none; 100 | } 101 | .content__select option { 102 | color: #333; 103 | font-size: 18px; 104 | } 105 | .content__select--arrow { 106 | position: absolute; 107 | width: 10px; 108 | height: 10px; 109 | margin: 30px 0px 0px -25px; 110 | } 111 | 112 | .box-area { 113 | text-align: center; 114 | } 115 | .info-box { 116 | max-width: 330px; 117 | margin: 15px 20px; 118 | display: inline-block; 119 | background-color: #fcfcfc; 120 | animation: opac 1.5s; 121 | } 122 | .info-box__top { 123 | height: 250px; 124 | overflow: hidden; 125 | } 126 | .info-box__img { 127 | height: 100%; 128 | } 129 | .info-box__view-name { 130 | color: #fff; 131 | font-size: 20px; 132 | left: 25px; 133 | position: absolute; 134 | text-align: left; 135 | text-shadow: 1px 1px 1px #000; 136 | top: 70%; 137 | } 138 | .info-box__area-name { 139 | color: #fff; 140 | font-size: 16px; 141 | left: 25px; 142 | position: absolute; 143 | text-align: left; 144 | text-shadow: 1px 1px 1px #000; 145 | top: 86%; 146 | } 147 | .info-box__bottom { 148 | padding: 20px 20px 0px 20px; 149 | } 150 | .info-box__span { 151 | font-size: 14px; 152 | overflow: hidden; 153 | text-overflow: ellipsis; 154 | white-space: nowrap; 155 | text-align: left; 156 | display: block; 157 | padding: 3px; 158 | } 159 | .info-box__button { 160 | padding: 15px; 161 | background-color: #333; 162 | margin: 10px; 163 | } 164 | .modal { 165 | z-index: 3; 166 | display: none; 167 | padding: 50px 0px; 168 | position: fixed; 169 | left: 0; 170 | top: 0; 171 | width: 100%; 172 | height: 100%; 173 | overflow: auto; 174 | background-color: rgb(0, 0, 0); 175 | background-color: rgba(0, 0, 0, 0.4); 176 | animation: opac 0.8s; 177 | letter-spacing: 2px; 178 | } 179 | .modal__container { 180 | margin: auto; 181 | background-color: #fff; 182 | position: relative; 183 | padding: 0; 184 | outline: 0; 185 | max-width: 600px; 186 | } 187 | .modal__header { 188 | color: #fff; 189 | background-color: #42ab9e; 190 | padding: 10px; 191 | text-align: center; 192 | } 193 | .modal__body { 194 | padding: 20px 50px; 195 | text-align: center; 196 | } 197 | .modal__body p { 198 | text-align: left; 199 | line-height: 24px; 200 | } 201 | .modal__img { 202 | max-width: 100%; 203 | } 204 | .modal__footer { 205 | color: #fff; 206 | background-color: #42ab9e; 207 | padding: 1px 10px; 208 | } 209 | .paging { 210 | margin: 30px 0px; 211 | text-align: center; 212 | user-select: none; 213 | } 214 | .paging__pages { 215 | display: inline-block; 216 | width: 45px; 217 | height: 45px; 218 | margin: 5px; 219 | color: #42ab9e; 220 | cursor: pointer; 221 | line-height: 45px; 222 | } 223 | .paging__pages--active { 224 | background-color: #42ab9e; 225 | color: #fff; 226 | border-radius: 100%; 227 | } 228 | .container__footer { 229 | border-top: solid 1px #dbe0df; 230 | background-color: #fff; 231 | padding: 10px; 232 | text-align: center; 233 | font-size: 15px; 234 | color: #202121; 235 | } 236 | @keyframes opac { 237 | from { 238 | opacity: 0 239 | } 240 | to { 241 | opacity: 1 242 | } 243 | } 244 | @media screen and (max-width: 690px) { 245 | .header { 246 | width: 75%; 247 | } 248 | .header__title { 249 | font-size: 26px; 250 | } 251 | .header__desc { 252 | font-size: 20px; 253 | } 254 | .content__select { 255 | width: 75%; 256 | } 257 | .info-box { 258 | width: 90%; 259 | } 260 | .info-box__view-name { 261 | top: 70%; 262 | float: none; 263 | } 264 | .info-box__area-name { 265 | top: 86%; 266 | right: unset; 267 | left: 25px; 268 | float: none; 269 | } 270 | .modal__container { 271 | width: 90%; 272 | } 273 | .modal__body { 274 | padding: 20px 30px; 275 | } 276 | } 277 | @media screen and (max-width: 480px) { 278 | .header__title { 279 | font-size: 20px; 280 | } 281 | .header__desc { 282 | font-size: 16px; 283 | } 284 | } -------------------------------------------------------------------------------- /data/viewZip.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "District": "中正區", "zipcode": "100" }, 3 | { "District": "大同區", "zipcode": "103" }, 4 | { "District": "中山區", "zipcode": "104" }, 5 | { "District": "松山區", "zipcode": "105" }, 6 | { "District": "大安區", "zipcode": "106" }, 7 | { "District": "萬華區", "zipcode": "108" }, 8 | { "District": "信義區", "zipcode": "110" }, 9 | { "District": "士林區", "zipcode": "111" }, 10 | { "District": "北投區", "zipcode": "112" }, 11 | { "District": "內湖區", "zipcode": "114" }, 12 | { "District": "南港區", "zipcode": "115" }, 13 | { "District": "文山區", "zipcode": "116" } 14 | ] -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Travel Map 10 | 11 | 12 | 13 |
14 |
15 |
16 |

臺北市-觀光景點

17 |

Javascript & CSS 練習

18 |
19 |
20 |
21 | 22 | 23 | 24 | 25 |
26 |
27 | 45 | 46 |
47 | 51 |
52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /js/all.js: -------------------------------------------------------------------------------- 1 | /****************************** 2 | * 20170505 3 | * GuaHsu(guaswork@gmail.com) 4 | * http://guastudio.com 5 | ******************************/ 6 | /** 7 | * 設定區 8 | */ 9 | var pageViewQty = 12; //每頁顯示的景點數量 10 | var noDataText = '未提供'; //無資料時顯示的文字 11 | 12 | /** 13 | * 替換字元 14 | * @param {string} str 傳入的字串 15 | * @param {string} find 要比對的字串 16 | * @param {string} replace 要替換的字串 17 | * replace的g代表全部,否則只改第一個比對到的字串 18 | */ 19 | function replaceAll(str, find, replace) { 20 | return str.replace(new RegExp(find, 'g'), replace); 21 | } 22 | 23 | /** 24 | * 取得台北市行政區資料 25 | * 透過AJAX取得JSON資料 26 | */ 27 | var areaData = ''; //全部的行政區資料(全域變數) 28 | function getAreaData() { 29 | var xhr = new XMLHttpRequest(); 30 | xhr.open('get', 'data/viewZip.json', true); 31 | xhr.send(null); 32 | xhr.onload = function() { 33 | if (xhr.status == 200) { 34 | areaData = JSON.parse(xhr.responseText); 35 | createAreaSelect(); //建立行政區下拉選單 36 | getViewData(); //取得景點資料 37 | }else { 38 | document.querySelector('.box-area').innerHTML = '

取得資料時發生錯誤,請檢查網路狀態並重新整理:)

'; 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * 取得行政區景點資料 45 | * 透過AJAX取得JSON資料 46 | */ 47 | var viewData = ''; 48 | function getViewData() { 49 | var xhr = new XMLHttpRequest(); 50 | xhr.open('get', 'data/viewData.json', true); 51 | xhr.send(null); 52 | xhr.onload = function() { 53 | if (xhr.status == 200) { 54 | viewData = JSON.parse(xhr.responseText); 55 | //檢查LocalStorage中是否有存在最後一筆紀錄(瀏覽器重新整理判斷用) 56 | var lastPage = JSON.parse(localStorage.getItem('lastPage')); 57 | if (lastPage) { 58 | selectArea(lastPage.areaName, lastPage.pageNo); 59 | } else { 60 | selectArea() 61 | } 62 | }else { 63 | document.querySelector('.box-area').innerHTML = '

取得資料時發生錯誤,請檢查網路狀態並重新整理:)

'; 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * 組合行政區下拉選單 70 | */ 71 | function createAreaSelect() { 72 | var areaOptions = ''; 73 | for (var i = 0; i < areaData.length; i++) { 74 | var areaName = areaData[i].District; 75 | var areaCode = areaData[i].areacode; 76 | areaOptions += ' '; 77 | } 78 | document.querySelector('.content__select').innerHTML = areaOptions; 79 | } 80 | 81 | /** 82 | * 設定景點資料 83 | * @param {*} areaName 行政區 84 | * @param {*} pageNo 頁碼 85 | */ 86 | var nowAreaName = ''; 87 | var nowViewData = []; 88 | var nowViewDataCnt = 0; 89 | function setViewData(areaName, pageNo) { 90 | if (nowAreaName != areaName) { 91 | nowAreaName = areaName 92 | nowViewData = []; 93 | for (var i = 0; i < viewData.length; i++) { 94 | var dataNo = viewData[i].RowNumber || noDataText; 95 | var dataAreaName = viewData[i].address.substr(5, 3) || noDataText; 96 | var dataTitle = viewData[i].stitle || noDataText; 97 | var dataDesc = viewData[i].xbody || noDataText; 98 | var dataMRT = viewData[i].MRT || noDataText; 99 | var dataInfo = viewData[i].info || noDataText; 100 | var dataOpenTime = viewData[i].MEMO_TIME || noDataText; 101 | var dataAddress = viewData[i].address || noDataText; 102 | var dataTel = viewData[i].MEMO_TEL || noDataText; 103 | //景點圖片處理,因有可能非陣列,對其進行陣列判斷 104 | if (viewData[i].file.img.length === undefined) { 105 | var dataPic = viewData[i].file.img['#text'] || ''; // 106 | var dataPicDesc = viewData[i].file.img['-description'] || noDataText; 107 | } else { 108 | var dataPic = viewData[i].file.img[0]['#text'] || ''; 109 | var dataPicDesc = viewData[i].file.img[0]['-description'] || noDataText; 110 | } 111 | if (dataAreaName == nowAreaName) { 112 | nowViewData.push({ 113 | 'dataNo': dataNo, 114 | 'dataAreaName': dataAreaName, 115 | 'dataTitle': dataTitle, 116 | 'dataDesc': dataDesc, 117 | 'dataMRT': dataMRT, 118 | 'dataInfo': dataInfo, 119 | 'dataOpenTime': dataOpenTime, 120 | 'dataAddress': dataAddress, 121 | 'dataTel': dataTel, 122 | 'dataPic': dataPic, 123 | 'dataPicDesc': dataPicDesc, 124 | }); 125 | } 126 | } 127 | nowViewDataCnt = nowViewData.length; 128 | } 129 | //檢查是否有傳入頁碼,若無則設為第一頁 130 | if (!pageNo) { 131 | pageNo = '1'; 132 | } 133 | createView(pageNo); //組出對應頁碼景點資料 134 | createPaging(pageNo); //組出分頁按鈕 135 | document.querySelector('.content__select').selectedOptions[0].text = nowAreaName; 136 | } 137 | 138 | /** 139 | * 建立景點 140 | * @param {*} pageNo 頁碼 141 | */ 142 | function createView(pageNo) { 143 | var viewBox = ''; 144 | if (pageNo == 1) { 145 | var pageStart = 0; 146 | var pageEnd = pageViewQty; 147 | } else { 148 | var pageStart = parseInt(pageNo * pageViewQty - pageViewQty); 149 | var pageEnd = parseInt(pageNo * pageViewQty); 150 | } 151 | for (var i = pageStart; i < pageEnd; i++) { 152 | if (i == nowViewDataCnt) { 153 | break; 154 | } 155 | viewBox += '
' + 156 | '
' + 157 | '' + nowViewData[i].dataPicDesc + '' + 158 | '' + nowViewData[i].dataTitle + '' + 159 | '' + nowAreaName + '' + 160 | '
' + 161 | '
' + 162 | '地址:' + nowViewData[i].dataAddress + '' + 163 | '電話:' + nowViewData[i].dataTel + '' + 164 | '時間:' + nowViewData[i].dataOpenTime + '' + 165 | '
' + 167 | '
'; 168 | 169 | } 170 | document.querySelector('.box-area').innerHTML = viewBox; 171 | //進行景點資料按鈕功能綁定 172 | setReadMoreButton(); 173 | } 174 | 175 | /** 176 | * 景點資料按鈕功能綁定 177 | */ 178 | function setReadMoreButton() { 179 | var readMoreButton = document.querySelectorAll('.button--readMore'); 180 | for (var i = 0; i < readMoreButton.length; i++) { 181 | readMoreButton[i].addEventListener('click', function(e) { 182 | showViewData(e.srcElement.getAttribute('data-viewNo')); 183 | }, false); 184 | } 185 | } 186 | 187 | /** 188 | * 組合分頁按鈕 189 | * @param {*} pageNo 頁碼 190 | */ 191 | function createPaging(pageNo) { 192 | var page = ''; 193 | //計算分頁數量(當前景點總數/每頁要顯示的景點數量,無條件進位) 194 | var pageCnt = Math.ceil(nowViewDataCnt / pageViewQty); 195 | if (pageCnt > 1) { 196 | for (var i = 0; i < pageCnt; i++) { 197 | var setNo = parseInt(i + 1); 198 | if (pageNo == setNo) { 199 | page += '
  • ' + setNo + '
  • '; 200 | } else { 201 | page += '
  • ' + setNo + '
  • '; 202 | } 203 | } 204 | document.querySelector('.paging').innerHTML = page; 205 | //綁定分頁按鈕功能 206 | setPageButton() 207 | } 208 | } 209 | 210 | /** 211 | * 綁定分頁按鈕功能 212 | */ 213 | function setPageButton() { 214 | var pageEl = document.querySelectorAll('.paging__pages'); 215 | for (var i = 0; i < pageEl.length; i++) { 216 | pageEl[i].addEventListener('click', function(e) { 217 | pageNo = e.srcElement.textContent; 218 | window.scrollTo(0, 300); 219 | //寫入瀏覽紀錄 220 | setHistory(nowAreaName, pageNo); 221 | //建立對應頁碼的景點資料 222 | createView(pageNo); 223 | //建立對應頁碼的分頁按鈕 224 | createPaging(pageNo); 225 | }, false); 226 | } 227 | } 228 | 229 | /** 230 | * 選擇行政區功能 231 | * @param {*} areaName 行政區 232 | * @param {*} pageNo 頁碼 233 | */ 234 | function selectArea(areaName, pageNo) { 235 | //如果沒傳入頁碼,則頁碼為1,並儲存瀏覽紀錄 236 | if (!pageNo) { 237 | pageNo = 1; 238 | areaName = document.querySelector('.content__select').selectedOptions[0].text; 239 | setHistory(areaName, pageNo); 240 | } 241 | //建立對應的景點資料 242 | setViewData(areaName, pageNo) 243 | } 244 | 245 | /** 246 | * 寫入瀏覽紀錄 247 | * @param {*} areaName 行政區 248 | * @param {*} pageNo 頁碼 249 | * 紀錄當前行政區及頁數,並儲存在history及localstorage中 250 | */ 251 | function setHistory(areaName, pageNo) { 252 | var historyData = '{"areaName":"' + areaName + '","pageNo":' + pageNo + '}'; 253 | history.pushState(historyData, '', ''); 254 | localStorage.setItem('lastPage', historyData); 255 | } 256 | 257 | /** 258 | * 處理瀏覽器上下頁行為 259 | * 當觸發時,檢查history.state是否有值 260 | * true:寫入localStorage中,並取出來組出對應的景點資料 261 | * false:直接回到上一頁 262 | */ 263 | window.onpopstate = function(e) { 264 | if (e.state) { 265 | localStorage.setItem('lastPage', e.state); 266 | var historyData = JSON.parse(e.state); 267 | //組出對應的景點資料 268 | setViewData(historyData.areaName, historyData.pageNo); 269 | } else { 270 | history.back(); 271 | } 272 | } 273 | 274 | /** 275 | * 顯示景點資料框 276 | * @param {*} viewNo 景點編號 277 | */ 278 | function showViewData(viewNo) { 279 | for (var i = 0; i < nowViewData.length; i++) { 280 | if (viewNo == nowViewData[i].dataNo) { 281 | document.querySelector('.view-title').textContent = nowViewData[i].dataTitle; 282 | document.querySelector('.view-img').setAttribute('src', nowViewData[i].dataPic); 283 | document.querySelector('.view-img').setAttribute('alt', nowViewData[i].dataPicDesc); 284 | document.querySelector('.view-content').innerHTML = replaceAll(nowViewData[i].dataDesc, '。', '。
    '); 285 | document.querySelector('.view-add').innerHTML = nowViewData[i].dataAddress; 286 | document.querySelector('.view-tel').innerHTML = nowViewData[i].dataTel; 287 | document.querySelector('.view-time').innerHTML = replaceAll(nowViewData[i].dataOpenTime, /\n/g, '
    '); 288 | document.querySelector('.view-MRT').textContent = nowViewData[i].dataMRT; 289 | document.querySelector('.view-info').innerHTML = replaceAll(nowViewData[i].dataInfo, '。', '。
    '); 290 | break; 291 | } 292 | } 293 | //顯示景點資料框 294 | document.querySelector('.modal').style.display = 'block'; 295 | } 296 | 297 | /** 298 | * 偵測景點資料框關閉 299 | */ 300 | function listenModalClose() { 301 | var closeModalEl = document.querySelector('.modal'); 302 | closeModalEl.addEventListener('click', function(e) { 303 | if (e.target.className == 'modal' || e.target.classList.contains('button--close')) { 304 | document.querySelector('.modal').style.display = 'none'; 305 | } 306 | }) 307 | } 308 | 309 | /** 310 | * 偵測行政區選擇框 311 | */ 312 | function listenSelect() { 313 | var selectEl = document.querySelector('.content__select'); 314 | selectEl.addEventListener('change', selectArea, false); 315 | } 316 | 317 | /** 318 | * 啟動程式 319 | */ 320 | function goStart() { 321 | //取得行政區資料 322 | getAreaData(); 323 | //偵測行政區選擇框 324 | listenSelect(); 325 | //偵測行政區選擇框 326 | listenModalClose(); 327 | } 328 | 329 | //啟動程式 330 | window.onload = goStart(); -------------------------------------------------------------------------------- /js/demo_ga.js: -------------------------------------------------------------------------------- 1 | (function (i, s, o, g, r, a, m) { 2 | i['GoogleAnalyticsObject'] = r; 3 | i[r] = i[r] || function () { 4 | (i[r].q = i[r].q || []).push(arguments) 5 | }, i[r].l = 1 * new Date(); 6 | a = s.createElement(o), 7 | m = s.getElementsByTagName(o)[0]; 8 | a.async = 1; 9 | a.src = g; 10 | m.parentNode.insertBefore(a, m) 11 | })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga'); 12 | ga('create', 'UA-84594235-4', 'auto'); 13 | ga('send', 'pageview'); 14 | --------------------------------------------------------------------------------