├── .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 | 
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 |
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 |
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 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
29 |
33 |
34 |
![]()
35 |
景點介紹:
36 |
地址:
37 |
電話:
38 |
開放時間:
39 |
鄰近捷運站:
40 |
交通資訊:
41 |
42 |
43 |
44 |
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 + '](' + nowViewData[i].dataPic + ')
' +
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 |
--------------------------------------------------------------------------------