我是一個前端小白叮趴,開始了解前端不過幾個月的時間,最近在參加360的前端星計劃友瘤。此文主要整理了我做任務(wù)的過程中走過的路翠肘、思考的問題,如果你和我一樣剛接觸前端辫秧,發(fā)現(xiàn)網(wǎng)上大多只是干枯枯的代碼的話束倍,那這篇文章對你可能有幫助。
用時32小時35分鐘盟戏,我并未實(shí)現(xiàn)我最初的美好設(shè)想:「可以不只實(shí)現(xiàn)題目要求绪妹,還可以做一下Google日歷那樣具有拖拽效果的日程管理功能」,只完成了最簡單的功能柿究,不過我踩過的坑邮旷,恰好是初學(xué)者可以參考的注意點(diǎn)。但我有的看法和思路不是很符合規(guī)范蝇摸,僅僅作為參考即可婶肩。也希望有大神不吝賜教办陷、批評指正,我虛心改正律歼。
因?yàn)橄虢璐遂柟袒A(chǔ)知識民镜,所以專注點(diǎn)放在了細(xì)節(jié)和基礎(chǔ)上,我所有的代碼都是手寫(打)的苗膝,用原生JavaScript殃恒,有的地方可能略微化簡為繁,但有些細(xì)節(jié)深究起來是有意義的辱揭,文末我會整理一下所有的參考資料,我寫的源代碼見JS_Calendar病附。強(qiáng)烈求 Star ?(?*)
下面以時間線來寫寫问窃,我的解決思路、解決過程完沪、學(xué)習(xí)收獲域庇。
1. 需求分析
盡管題目(用js實(shí)現(xiàn)萬年歷)初看起來并不復(fù)雜,但還是先進(jìn)行需求分析覆积,也就是明確一下听皿,我的日歷要實(shí)現(xiàn)的功能點(diǎn),以便后期有序的行動宽档。
我的思考結(jié)果如下:
- 選擇年尉姨、月,顯示此年此月日歷(列表選擇吗冤、上下按鈕)
- 選擇日期又厉,顯示當(dāng)日(選擇日)信息:
- year-month-day
- 星期
- 農(nóng)歷年月日
- 星座
- 歷史上的今天
- 返回到今天
- 顯示當(dāng)前時間(實(shí)時變化)
- 日歷中顯示主要假期
- 可選擇的假期列表,并在日歷定位假期區(qū)間
得到這些基礎(chǔ)功能點(diǎn)之后椎瘟,開始看網(wǎng)上的日歷覆致,尋找是否有沒注意到但很重要的功能點(diǎn),同時也研究UI為下一步作儲備肺蔚,后期遇到問題時都可以研究一下他們是怎么處理的(如寫HTML頁面時會疑惑要用table還是list實(shí)現(xiàn)……)
下面列舉了可供參考的日歷:
- 歷史上的今天
- 360萬年歷
- Bing引用的日歷-有趣網(wǎng)
- 農(nóng)歷網(wǎng)日歷
-
便民查詢網(wǎng)日歷
還有一些需要注冊才能使用的日歷 - 365日歷
- 滴答清單高級賬戶可用日歷
- Google日歷
補(bǔ)充完善之后煌妈,在此基礎(chǔ)上進(jìn)行下一步。
2.原型設(shè)計
存在這樣的結(jié)構(gòu):HTML → CSS → JavaScript
分別是內(nèi)容宣羊、樣式璧诵、行為三層的結(jié)構(gòu),所以我第一步先確定內(nèi)容段只。其實(shí)就是根據(jù)上一步得到的需求腮猖,提煉出HTML頁面里需要的內(nèi)容,同時考慮到用div分塊赞枕,于是提取內(nèi)容如下:
- Navigate
- 北京時間
- 年月選項(xiàng)
- 回到今天
- 今年假期
- Calendar
- 日期排列
- 農(nóng)歷日期節(jié)氣
- 國際節(jié)假日標(biāo)識
- Today
- 日期:YYYY-MM-DD
- 星期
- 日期:DAY
- 農(nóng)歷日期:生肖-月-初幾
- 天干地支日期:年-月-日
- 星座
- 宜&忌
- 歷史上的今天
這樣一來澈缺,大概有了HTML的雛形(nav坪创、calendar、today三個部分)姐赡,就開始考慮如何布局了莱预。我畫了一個自己想的,當(dāng)然因?yàn)榍捌诳催^了不少的例子项滑,所以看起來都是大同小異依沮。但重點(diǎn)在于,自己畫的過程枪狂,也能幫助自己想清楚每個部分是怎么回事危喉。
然后就開工了州疾。我列了一下要做的事辜限,有的是后來逐步添加進(jìn)去的,作為一個整體概覽严蓖,提醒自己下一步要做什么薄嫡。
3.實(shí)現(xiàn)細(xì)節(jié)
代碼歷史版本可以到Github去看,我基本是每天commit一次颗胡,和我初期的思路一樣毫深,總體按照如下流程來完成:
- .html初版,涵蓋前面所述內(nèi)容
- CSS布局毒姨,實(shí)現(xiàn)想要的效果
- JavaScript哑蔫,這個階段會很多次返回去修改HTML和CSS
下面就以我遇到的問題為引線來說說寫日歷的編程細(xì)節(jié)。
如何讓散亂的div去到該去的位置手素?
這事應(yīng)該交由CSS來管鸳址,雖然我知道一些基礎(chǔ)的語法,但用起來還是不能隨心所欲泉懦,于是Google CSS+布局的第一個鏈接就拯救了我稿黍。學(xué)習(xí)CSS布局是一個關(guān)于CSS基礎(chǔ)的網(wǎng)站,利于初學(xué)者理解學(xué)習(xí)的地方在于崩哩,網(wǎng)站把講解和演示融為了一體巡球,可以在閱讀的過程中,隨時通過放大縮小瀏覽器顯示比例來感受不同的效果邓嘹。
看完一遍之后酣栈,就開始對我的頁面進(jìn)行調(diào)整嘗試,最后得到了這樣的布局樣式:
嘗試的過程就不細(xì)說了汹押,就是很樸素的方法:反復(fù)試矿筝。
HTML里面DOM樹如下:
主要(不是全部)起作用的代碼如下:
#calendar_s {
width:80%;
height:100%;
color:#e25c38;
margin:auto;
}
nav {
position:relative;
margin-left:0;
top:0;
height:20%;
background-color:#86a9c0;
}
#calendar {
float:left;
width:70%;
height:80%;
top:0;
bottom:0;
left:0;
background-color:#E9967A;
}
#todayInfo {
float:right;
width:30%;
height:80%;
top:0;
background-color:#FAEBD7;
}
思路則是使nav區(qū)域吸附到頂部(top:0)并保持高度(height:20%),余下的calendar和todayInfo兩塊分別左右浮動棚贾,占滿剩下的區(qū)域窖维。從3D視圖看過去大概是這個模樣:
日歷中日期部分用什么榆综?table還是list?
這是我在寫日歷主體部分時候的疑惑铸史,兩種方式各有各的特點(diǎn)鼻疮,現(xiàn)有的日歷既有table的也有l(wèi)ist的,到現(xiàn)在我仍然覺得兩種都可以實(shí)現(xiàn)想要的效果琳轿,只是相應(yīng)的CSS側(cè)重點(diǎn)會不一樣判沟。受到上個問題中CSS布局網(wǎng)站的影響,我希望不采用絕對定位和寬度崭篡,在不同大小的瀏覽器中可以很好的顯示挪哄,而table可以保證行高相同,同時用兩層for循環(huán)生成table容易理解琉闪,所以我采用了table中燥。
在HTML里寫出主體的table結(jié)構(gòu)以及表頭thead:
<div id="calendar">
<table id="dateZone">
<thead>
<tr>
<td>日</td>
<td>一</td>
<td>二</td>
<td>三</td>
<td>四</td>
<td>五</td>
<td>六</td>
</tr>
</thead>
<tbody id="dateTable">
</tbody>
</table>
</div>
完成HTML之后,就需要JavaScript上場了塘偎。但我并不是很熟悉DOM可以如何操作,于是找了課程Coursera《HTML拿霉、CSS 和 JavaScript》中DOM部分來學(xué)習(xí)吟秩,然后再自己寫一遍像下面這樣的代碼,就可以生成想要的HTML了绽淘。
//來源:Coursera課程
//實(shí)驗(yàn):DOM操作Node
function insert_new_text(){
var newText = document.createTextNode("this is an added text");// 新建文本元素
var textPart = document.getElementById("jfksdj"); //獲取節(jié)點(diǎn)
textPart.appendChild(newText); //使newText成為textPart的子節(jié)點(diǎn)
}
function insert_new_node(){
var newItem = document.createElement("td"); //新建元素節(jié)點(diǎn)
var destParent = document.getElementsByTagName("body")[0]; //通過tag獲取標(biāo)簽
destParent.insertBefore(newItem, destParent.firstChild); //在destParent的第一個子節(jié)點(diǎn)之前插入newItem
}
下面就是我用JavaScript生成tbody部分的代碼:
function generateTable() {
for (var i = 0; i < 6; i++){
var newRow = document.createElement("tr");
for(var j = 0; j < 7; j++){
var newDate = document.createElement("td");
var date;//獲取日期信息
newDate.innerText = date;
newRow.appendChild(newDate);
}
dateTable.appendChild(newRow);
}
}
表格可以生成了涵防,但這只能生成一個外殼,真正的日歷內(nèi)容還沒有解決沪铭,那么現(xiàn)在的問題就變成了:
如何知道每月日歷的所有日期壮池?
首先去查了一下原生JavaScript所提供給我們的關(guān)于時間的對象Date,參考MDN杀怠,可以發(fā)現(xiàn)JavaScript能讀取瀏覽器本地的當(dāng)前時間數(shù)據(jù)椰憋,也就是我們可以獲得“今天”的年、月赔退、日橙依、星期,那么根據(jù)這些信息能否推算出6*7表格中每個位置的日期值呢硕旗?
Google了很多窗骑,當(dāng)然不是要去抄代碼,而且與其去看沒有解釋的復(fù)雜代碼漆枚,還不如自己思考创译。最后我在一個博客里有了靈感,博主寫了一篇叫做How to build simple calendar with JavaScript的文章墙基。他在文章開頭說:
While there are lots of JavaScript-based calendar widgets out there, there's not much in the way of explaining how they work for the JS acolyte. I recently had the opportunity of building one from memory (and best of all, for no particular reason), using none of the popular JS libraries. This is the tutorial I wish I had found five years ago.
不得不說因?yàn)檫@個博客才有了你正在看的這篇文章软族,其中主要是這段話激勵了我刷喜。
回到日歷的生成上來,我的靈感是怎么來的呢互订?上面這篇文章中詳細(xì)講了生成當(dāng)前這個月的日歷的實(shí)現(xiàn)過程吱肌,文末博主說他這周會繼續(xù)教大家做更高級的日歷,但有趣的是仰禽,他在4個月之后才更新了博客(文章鏈接)氮墨,說自己“好久沒更新了”……然后附上網(wǎng)友對他上一篇文章的建議,就是這些建議吐葵,讓我知道了我該怎么做规揪。
Michiel van der Blonk suggested a way to determine the number of days in a month:
var d = new Date(nYear, nMonth + 1, 0); // nMonth is 0 thru 11return(d.getDate());
The mechanism here is the Date constructor will attempt to convert year, month and day values to the nearest valid date. So in the above code, nMonth is incremented to find the following month, and then a Date object is created with zero days. Since this is an invalid date, the Date object defaults to the last valid day of the previous month — which is the month we're interested in to begin with. Then getDate() returns the day (1-31) of that date — voilá, we have the number of days.
Bonus: it also seems to automatically correct for leap year. It feels like a hack but it seems to work consistently.
這里提到了一件事,Date這個對象會返回離參數(shù)最近的有效日期温峭,利用這點(diǎn)就可以處理閏年猛铅、跨月份的日期問題》锊兀回去再看MDN文檔奸忽,的確如此:
Note: Date需要調(diào)用多個參數(shù)的構(gòu)造函數(shù),當(dāng)數(shù)值大于合理范圍時(如月份為13或者分鐘數(shù)為70)揖庄,會被調(diào)整為相鄰值栗菜。比如 new Date(2013, 13, 1)會等于 new Date(2014, 1, 1),還會新建一個2014-02-01的日期(注意月份是從0開始的)蹄梢。
所以我想到的方法是這樣:獲取此年此月1號是周幾(getDay()
)疙筹,然后用1減去這個值,賦給Date后就能得到所需的日歷開始日期禁炒,但如果1號是周日的話而咆,那就不需要減了,畫了一下流程圖:
但在后來的過程中我發(fā)現(xiàn)幕袱,如果第一行周日是1號的話暴备,那么周日對應(yīng)的getDay()值是0,這時候就不需要判斷了凹蜂。最后寫出來的代碼是這個樣子:
var month = currentDate.getMonth();
var year = currentDate.getFullYear();
var thisMonthDay = new Date(year, month, 1);
var thisMonthFirstDay = thisMonthDay.getDay();
var thisMonthFirstDate = new Date(year, month, - thisMonthFirstDay);
generateTable(thisMonthFirstDate); //生成日歷主體的日期區(qū)域
generateTable(firstDate)中for循環(huán)內(nèi)部增加的部分:
//獲取日期信息
firstDate.setDate(++date);
date = firstDate.getDate();
這里要提的一點(diǎn)是馍驯,我把上面改成了new Date(year, month, - thisMonthFirstDay)
,是因?yàn)槲艺{(diào)試的過程中玛痊,當(dāng)我寫成firstDate.setDate(date++);
的時候汰瘫,第一個值會出現(xiàn)問題,同時為了代碼更簡潔(其實(shí)是為了我好理解)擂煞,就改成了- thisMonthFirstDay與++date的搭配混弥,這樣一來,可以免去++未執(zhí)行的可能。(寫到這里才發(fā)現(xiàn)我可能對這里的理解不夠蝗拿,當(dāng)時試著改了一下就成功了晾捏,沒有細(xì)想,竟然忘記了具體的問題是什么了)
至此哀托,已經(jīng)可以生成一個6*7table的日歷惦辛,你可以想到我們很容易通過改參數(shù)來生成不同月份的日歷,也就是說“上一月”這樣的功能仓手,可以在按鈕上添加事件監(jiān)聽胖齐,然后只需要設(shè)定調(diào)用函數(shù)時使用的參數(shù)就可以了。
為了避免這篇文章太長(我沒想到寫了這么多還沒寫完……)嗽冒,后面的部分我用另一篇文章來說(如果有必要的話)呀伙。