基于Web?Components實(shí)現(xiàn)一個(gè)日歷原生組件
接到一個(gè)需求,需要在原生的頁(yè)面實(shí)現(xiàn)一個(gè)日歷功能,正好之前利用閑暇時(shí)間用js寫(xiě)了一個(gè)萬(wàn)年歷在github吃灰, 再用web components封裝一下,完美的一個(gè)組件就誕生了。
Web Components 基礎(chǔ)
Web Components API相對(duì)于其他UI框架, 不用加載其他模塊, 原裝組件更簡(jiǎn)單,代碼量更小.API很多,這里只介紹本次封裝組件使用到的
template標(biāo)簽
template標(biāo)簽在頁(yè)面加載時(shí)該標(biāo)簽中的內(nèi)容不會(huì)顯示,需要使用js來(lái)使其展示在頁(yè)面上。標(biāo)簽里面可以包含css 和 組件html結(jié)構(gòu)。
日歷組件template 內(nèi)容: 包括日歷樣式css, 日歷組件html結(jié)構(gòu), 預(yù)留可擴(kuò)充功能的插槽
let template = document.createElement("template");
template.innerHTML = `<style>
* {
user-select: none;
list-style: none;
margin: 0;
padding: 0;
}
.calendar_wrap {
width: 1280px;
margin: 100px auto;
position: relative;
padding-bottom: 10px;
border-radius: 20px;
box-shadow: 10px 10px 50px 10px #ccc;
}
.pick_year_month {
width: 100%;
height: 80px;
line-height: 80px;
text-align: center;
background: #cdb092;
border-radius: 20px 20px 0 0;
}
.pick_year_item {
margin: 0 20px;
font-size: 24px;
font-weight: 600;
}
.date_action {
cursor: pointer;
display: inline-block;
width: 30px;
height: 30px;
border-radius: 50%;
text-align:center;
line-height: 30px;
background: #d6d4d4;
color: #b1afaa;
font-size: 24px;
margin: 0 4px;
}
.change_num_txt {
color: #000;
}
.weekday,
.month_days {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
}
.weekday_item,
.days_item {
width: 80px;
margin: 0 50px;
text-align: center;
font-size: 20px;
}
.weekday {
margin-bottom: 20px;
font-weight:600;
}
.days_item {
height: 80px;
margin-bottom: 10px;
padding-top: 5px;
text-align: center;
line-height: 80px;
font-size: 24px;
font-weight: bold;
}
.day_color {
color: #5678FE;
background: #bb7665;
color: #fff;
border-radius: 50%;
}
.gray_color {
color: #acacac;
}
.red_color {
color: #c26228;
}
.blue_color {
color: #c26228;
}
.real_day {
cursor: pointer;
}
.real_day:hover {
outline: 1px solid orange;
border-radius: 5px;
box-shadow: 0 0 2px 2px orange;
}
.select_day_active {
outline: 1px solid orange;
}
</style>
<div class="calendar_wrap" id="calendar_container">
<div class="pick_year_month">
<span class="pick_year_item" id="decrease_year">? </span>
<span class="pick_year_item" id="decrease_month">? </span>
<span class="pick_year_item"><span class="change_num_txt" id="year_num">2019</span>年<span class="change_num_txt" id="month_num">1</span>月
</span>
<span class="pick_year_item" id="increase_month"> ?</span>
<span class="pick_year_item" id="increase_year"> ?</span>
</div>
<ul class="weekday">
<li class="weekday_item blue_color">日</li>
<li class="weekday_item">一</li>
<li class="weekday_item">二</li>
<li class="weekday_item">三</li>
<li class="weekday_item">四</li>
<li class="weekday_item">五</li>
<li class="weekday_item blue_color">六</li>
</ul>
<ul class="month_days" id="getdays">
</ul>
<slot name="customBox"></slot>
</div>自定義元素類(lèi)
自定義元素CustomCalendar, 繼承HTMLElement父類(lèi), 因此具有html元素特性
class CustomCalendar extends HTMLElement {
}接下來(lái)需要在自定元素類(lèi)中加載剛才定義好的template內(nèi)容
class CustomCalendar extends HTMLElement {
#shadowDom
constructor() {
super();
this.#shadowDom = this.attachShadow({ mode: "open"});
this.#shadowDom.appendChild(template.content.cloneNode(true));
}
}
customElements.define('a-calendar', CustomCalendar);this.attachShadow() 控制影子DOM 是否可操作。返回一個(gè)影子DOM(this.#shadowDom 可以用來(lái)操作DOM)。影子DOM表示內(nèi)部元素與外部隔離,不受外部任何影響。
- mode: "closed" 內(nèi)部元素可見(jiàn),外部無(wú)法操作DOM
- mode: "open" 內(nèi)部元素可見(jiàn),外部可操作DOM
這里使用template.content.cloneNode(true) 克隆一份template內(nèi)容加載到自定義元素內(nèi)是因?yàn)榭赡苡?strong>多個(gè)自定義元素的實(shí)例,這個(gè)模板內(nèi)容還要留給其他實(shí)例使用,所以不能直接移動(dòng)它的子元素
瀏覽器原生方法 customElements.define('a-calendar', CustomCalendar) 表示了自定義元素a-calendar 與自定義類(lèi)CustomCalendar的關(guān)聯(lián),然后就可以在HTML結(jié)構(gòu)中使用a-calendar標(biāo)簽了,在template中,我們還預(yù)留了插槽的功能,還可以使用插槽來(lái)自行擴(kuò)展組件
<a-calendar>
<div slot="customarea">111</div>
</a-calendar>父子組件傳值
在之前定義了自定義標(biāo)簽,在html中使用,還可以像Vue React組件那樣在標(biāo)簽上添加任意屬性
<a-calendar config="123"></a-calendar>
父組件向子組件傳遞值
和其他UI框架組件一樣, 父組件傳值給子組件也是直接在標(biāo)簽上添加要傳遞的屬性,只是在子組件接收傳值這塊,web components 處理有些出入。
在自定義類(lèi)CustomCalendar中,可以通過(guò)attributeChangedCallback鉤子函數(shù)來(lái)監(jiān)聽(tīng)標(biāo)簽上屬性值的變化,同時(shí)需要observedAttributes來(lái)注冊(cè)需要監(jiān)聽(tīng)的屬性, observedAttributes返回一個(gè)數(shù)組,只有在數(shù)組中填入的屬性名字,屬性值變化后才會(huì)在attributeChangedCallback拿到對(duì)應(yīng)的值
class CustomCalendar extends HTMLElement {
#shadowDom
constructor() {
super();
this.#shadowDom = this.attachShadow({ mode: "open"});
this.#shadowDom.appendChild(template.content.cloneNode(true));
}
// 監(jiān)聽(tīng)屬性attr 值變化
attributeChangedCallback(name, olddata, newData) {}
static get observedAttributes() {return ['config']; }
}子組件傳值給父組件
可以通過(guò)自定義事件來(lái)實(shí)現(xiàn),在子組件需要傳值的時(shí)刻,觸發(fā)自定義事件,父組件監(jiān)聽(tīng)自定義事件,就能拿到傳值
在日歷組件中,我們初始化好了當(dāng)前的日期,需要向父組件傳遞這個(gè)日期,子組件觸發(fā)一個(gè)自定義yearmonthchange事件
class CustomCalendar extends HTMLElement {
#shadowDom
constructor() {
super();
this.#shadowDom = this.attachShadow({ mode: "open"})
this.#shadowDom.appendChild(template.content.cloneNode(true));
this.date = new Date(); //獲取當(dāng)前日期
this.year = this.date.getFullYear();
this.curYear = this.year;
this.month = this.date.getMonth() + 1;
this.curMonth = this.date.getMonth() + 1; // 當(dāng)前月份
this.day = this.date.getDate();
// 觸發(fā)自定義事件, 將年月傳遞給父組件
this.dispatchEvent(new CustomEvent('yearmonthchange', {detail: {year: this.year, month: this.month}}))
}
// 監(jiān)聽(tīng)屬性attr 值變化
attributeChangedCallback(name, olddata, newData) {}
static get observedAttributes() {return ['config']; }
}在父組件監(jiān)聽(tīng)yearmonthchange事件,獲取到傳值
<a-calendar id="cal" config="123"></a-calendar>
<script>
var cal = document.getElementById('cal');
cal.addEventListener('yearmonthchange', (e) => {
console.log(e.detail)
})
</script>日歷功能實(shí)現(xiàn)
實(shí)現(xiàn)好的日歷大致是這個(gè)樣子

以上介紹了基本的Web Components 使用, 搭好了組件的基本框架,接下來(lái)只需要把日歷的功能代碼填入到自定義類(lèi)中就OK了。
初始化事件
一共五個(gè)點(diǎn)擊事件:增減年月, 點(diǎn)擊日期
changeCurrentYearMonth(op, type) {
this.month_days_wrap.innerHTML = '';
if (op === 'inc') {
this[type] = this[type] + 1;
if (type==='month' && this[type] > 12) {
this[type] = 1
this.year = this.year + 1;
this.year_change_txt.innerHTML = this.year;
}
} else {
this[type] = this[type] - 1;
if (type==='month' && this[type] === 0) {
this[type] = 12
this.year = this.year - 1;
this.year_change_txt.innerHTML = this.year;
}
}
var opEle = type + '_change_txt';
this[opEle].innerHTML = this[type];
this.dispatchEvent(new CustomEvent('change', {detail: {year: this.year, month: this.month}}))
this.getMonthDays(this.year, this.month);
}
clickDayItem(day) {
// 子組件向父組件傳遞值
this.dispatchEvent(new CustomEvent('dayClick', day))
}
addEvent () {
var container = this.#shadowDom.getElementById('container');
var self = this;
container.addEventListener('click', function(e){
switch (e.target.id) {
case 'increase_year':
self.changeCurrentYearMonth('inc', 'year');
break;
case 'decrease_year':
self.changeCurrentYearMonth('dec', 'year');
break;
case 'increase_month':
self.changeCurrentYearMonth('inc', 'month');
break;
case 'decrease_month':
self.changeCurrentYearMonth('dec', 'month');
break;
}
if(e.target.className.includes('real_day')) {
self.clickDayItem(e.target.innerHTML);
}
})
}初始化日歷
將當(dāng)前的日期填入到頂部的操作欄中
initDays () {
this.month_days_wrap = this.#shadowDom.getElementById('getdays');
this.year_change_txt = this.#shadowDom.getElementById('year_num');
this.month_change_txt = this.#shadowDom.getElementById('month_num');
this.year_change_txt.innerHTML = this.year;
this.month_change_txt.innerHTML = this.month;
this.getMonthDays(this.year, this.month);
}然后開(kāi)始計(jì)算每個(gè)月的天數(shù)填入到每月的布局中
getMonthDays(yearNums, monthNums) {
// 當(dāng)月最后一天
var date1 = new Date(yearNums, monthNums, 0);
// 當(dāng)月第一天
var date2 = new Date(yearNums, monthNums - 1, 1);
// 上個(gè)月最后一天
var date3 = new Date(yearNums, monthNums - 1, 0);
var last_month_day = date3.getDate(); // 日期
var insert_before_nums = date2.getDay(); // 當(dāng)月第一天星期
var insert_after_nums = date1.getDay(); // 當(dāng)月最后一天星期
var days = date1.getDate();
for (var i = 1; i <= days; i++) {
let dates = new Date(yearNums, monthNums - 1, i);
var li = document.createElement('li');
li.className = 'days_item real_day';
li.innerHTML = i;
var isWeekend = dates.getDay();
if (isWeekend === 0 || isWeekend === 6) { // 周六日
li.className = 'days_item real_day blue_color';
}
this.month_days_wrap.appendChild(li);
}
if (monthNums === this.curMonth && yearNums === this.curYear) { //今日日期顯示色
var today = this.#shadowDom.querySelectorAll('.days_item')[this.day - 1];
today.className = "days_item real_day day_color"
}
for (let i = 0; i < insert_before_nums; i++) { // 上月后幾天
var days_one = this.#shadowDom.querySelectorAll('.days_item')[0];
var lis = document.createElement('li');
lis.className = 'days_item gray_color';
lis.innerHTML = last_month_day--;
this.#shadowDom.querySelector('.month_days').insertBefore(lis, days_one);
}
for (let i = 1; i <= 6 - insert_after_nums; i++) { // 下月前幾天
var lis = document.createElement('li');
lis.className = 'days_item gray_color';
lis.innerHTML = i;
this.#shadowDom.querySelector('.month_days').appendChild(lis);
}
}至此,日歷功能就完成了,我們可以把自定義類(lèi)單獨(dú)放在一個(gè)js文件,這樣使用的時(shí)候,直接引入js文件就可以在頁(yè)面上使用組件啦~~
以上就是基于Web Components實(shí)現(xiàn)一個(gè)日歷原生組件的詳細(xì)內(nèi)容,更多關(guān)于Web Components日歷組件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Javascript中克隆一個(gè)數(shù)組的實(shí)現(xiàn)代碼
這篇文章主要是對(duì)在Javascript中克隆一個(gè)數(shù)組的實(shí)現(xiàn)代碼進(jìn)行了介紹。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-12-12利用WebBrowser徹底解決Web打印問(wèn)題(包括后臺(tái)打?。?/a>
BS架構(gòu)下的打印大家是怎么解決的呢,我最近作了一個(gè)項(xiàng)目正好負(fù)責(zé)這一塊,不僅要求打印頁(yè)面的特定部分,還要求有后臺(tái)的批量打印,在網(wǎng)上查了一些資料,最后終于解決了。2009-06-06
JavaScript股票的動(dòng)態(tài)買(mǎi)賣(mài)規(guī)劃實(shí)例分析上篇
這篇文章主要介紹了JavaScript對(duì)于動(dòng)態(tài)規(guī)劃解決股票問(wèn)題的真題例舉講解。文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
js實(shí)現(xiàn)5秒倒計(jì)時(shí)重新發(fā)送短信功能
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)5秒倒計(jì)時(shí)重新發(fā)送短信功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02
JavaScript數(shù)據(jù)結(jié)構(gòu)常見(jiàn)面試問(wèn)題整理
在JavaScript中,數(shù)據(jù)結(jié)構(gòu)是指相互之間存在一種或多種特定關(guān)系的數(shù)據(jù)元素的集合,是帶有結(jié)構(gòu)特性的數(shù)據(jù)元素的集合。常用的數(shù)據(jù)結(jié)構(gòu)有:數(shù)組、列表、棧、隊(duì)列、鏈表、字典、集合等等2022-08-08
微信小程序?qū)崿F(xiàn)人臉識(shí)別登陸的示例代碼
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)人臉識(shí)別登陸的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
Js 回車(chē)換行處理的辦法及replace方法應(yīng)用
當(dāng)我們?cè)谖谋究蚶镙斎胛淖智孟禄剀?chē)后,希望在提交后網(wǎng)頁(yè)也顯示是換行的效果,可往往并不能如愿以?xún)敯?,?shí)在是憤怒啊自己寫(xiě)了一個(gè)回車(chē)換行處理的函數(shù),感興趣的朋友可以了解下啊,希望本文對(duì)你有所幫助2013-01-01
JavaScript控制圖片加載完成后調(diào)用回調(diào)函數(shù)的方法
這篇文章主要介紹了JavaScript控制圖片加載完成后調(diào)用回調(diào)函數(shù)的方法,實(shí)例分析了javascript回調(diào)函數(shù)的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03

