基于Web?Components實現(xiàn)一個日歷原生組件
接到一個需求,需要在原生的頁面實現(xiàn)一個日歷功能,正好之前利用閑暇時間用js寫了一個萬年歷在github吃灰, 再用web components封裝一下,完美的一個組件就誕生了。
Web Components 基礎
Web Components API相對于其他UI框架, 不用加載其他模塊, 原裝組件更簡單,代碼量更小.API很多,這里只介紹本次封裝組件使用到的
template標簽
template標簽在頁面加載時該標簽中的內(nèi)容不會顯示,需要使用js來使其展示在頁面上。標簽里面可以包含css 和 組件html結(jié)構(gòu)。
日歷組件template 內(nèi)容: 包括日歷樣式css, 日歷組件html結(jié)構(gòu), 預留可擴充功能的插槽
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>
自定義元素類
自定義元素CustomCalendar, 繼承HTMLElement父類, 因此具有html元素特性
class CustomCalendar extends HTMLElement { }
接下來需要在自定元素類中加載剛才定義好的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 是否可操作。返回一個影子DOM(this.#shadowDom 可以用來操作DOM)。影子DOM表示內(nèi)部元素與外部隔離,不受外部任何影響。
- mode: "closed" 內(nèi)部元素可見,外部無法操作DOM
- mode: "open" 內(nèi)部元素可見,外部可操作DOM
這里使用template.content.cloneNode(true) 克隆一份template內(nèi)容加載到自定義元素內(nèi)是因為可能有多個自定義元素的實例,這個模板內(nèi)容還要留給其他實例使用,所以不能直接移動它的子元素
瀏覽器原生方法 customElements.define('a-calendar', CustomCalendar) 表示了自定義元素a-calendar 與自定義類CustomCalendar的關聯(lián),然后就可以在HTML結(jié)構(gòu)中使用a-calendar標簽了,在template中,我們還預留了插槽的功能,還可以使用插槽來自行擴展組件
<a-calendar> <div slot="customarea">111</div> </a-calendar>
父子組件傳值
在之前定義了自定義標簽,在html中使用,還可以像Vue React組件那樣在標簽上添加任意屬性
<a-calendar config="123"></a-calendar>
父組件向子組件傳遞值
和其他UI框架組件一樣, 父組件傳值給子組件也是直接在標簽上添加要傳遞的屬性,只是在子組件接收傳值這塊,web components 處理有些出入。
在自定義類CustomCalendar中,可以通過attributeChangedCallback鉤子函數(shù)來監(jiān)聽標簽上屬性值的變化,同時需要observedAttributes來注冊需要監(jiān)聽的屬性, observedAttributes返回一個數(shù)組,只有在數(shù)組中填入的屬性名字,屬性值變化后才會在attributeChangedCallback拿到對應的值
class CustomCalendar extends HTMLElement { #shadowDom constructor() { super(); this.#shadowDom = this.attachShadow({ mode: "open"}); this.#shadowDom.appendChild(template.content.cloneNode(true)); } // 監(jiān)聽屬性attr 值變化 attributeChangedCallback(name, olddata, newData) {} static get observedAttributes() {return ['config']; } }
子組件傳值給父組件
可以通過自定義事件來實現(xiàn),在子組件需要傳值的時刻,觸發(fā)自定義事件,父組件監(jiān)聽自定義事件,就能拿到傳值
在日歷組件中,我們初始化好了當前的日期,需要向父組件傳遞這個日期,子組件觸發(fā)一個自定義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(); //獲取當前日期 this.year = this.date.getFullYear(); this.curYear = this.year; this.month = this.date.getMonth() + 1; this.curMonth = this.date.getMonth() + 1; // 當前月份 this.day = this.date.getDate(); // 觸發(fā)自定義事件, 將年月傳遞給父組件 this.dispatchEvent(new CustomEvent('yearmonthchange', {detail: {year: this.year, month: this.month}})) } // 監(jiān)聽屬性attr 值變化 attributeChangedCallback(name, olddata, newData) {} static get observedAttributes() {return ['config']; } }
在父組件監(jiān)聽yearmonthchange事件,獲取到傳值
<a-calendar id="cal" config="123"></a-calendar> <script> var cal = document.getElementById('cal'); cal.addEventListener('yearmonthchange', (e) => { console.log(e.detail) }) </script>
日歷功能實現(xiàn)
實現(xiàn)好的日歷大致是這個樣子
以上介紹了基本的Web Components 使用, 搭好了組件的基本框架,接下來只需要把日歷的功能代碼填入到自定義類中就OK了。
初始化事件
一共五個點擊事件:增減年月, 點擊日期
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); } }) }
初始化日歷
將當前的日期填入到頂部的操作欄中
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); }
然后開始計算每個月的天數(shù)填入到每月的布局中
getMonthDays(yearNums, monthNums) { // 當月最后一天 var date1 = new Date(yearNums, monthNums, 0); // 當月第一天 var date2 = new Date(yearNums, monthNums - 1, 1); // 上個月最后一天 var date3 = new Date(yearNums, monthNums - 1, 0); var last_month_day = date3.getDate(); // 日期 var insert_before_nums = date2.getDay(); // 當月第一天星期 var insert_after_nums = date1.getDay(); // 當月最后一天星期 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); } }
至此,日歷功能就完成了,我們可以把自定義類單獨放在一個js文件,這樣使用的時候,直接引入js文件就可以在頁面上使用組件啦~~
以上就是基于Web Components實現(xiàn)一個日歷原生組件的詳細內(nèi)容,更多關于Web Components日歷組件的資料請關注腳本之家其它相關文章!
相關文章
Javascript中克隆一個數(shù)組的實現(xiàn)代碼
這篇文章主要是對在Javascript中克隆一個數(shù)組的實現(xiàn)代碼進行了介紹。需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12

JavaScript股票的動態(tài)買賣規(guī)劃實例分析上篇

JavaScript數(shù)據(jù)結(jié)構(gòu)常見面試問題整理

JavaScript控制圖片加載完成后調(diào)用回調(diào)函數(shù)的方法