中秋国庆长假休完,又要投入到工作中了,做一个日历留念一下吧。
成果预览
按下右侧的“点击预览”按钮能够在以后页面预览,点击链接能够全屏预览。
https://codepen.io/comehope/pen/mdEyWEv
源代码下载
每日前端实战系列的全副源代码请从 github 下载:
https://github.com/comehope/front-end-daily-challenges
代码解读
这个日历的开发流程是,定义 DOM 构造之后,进行页面整体布局,绘制出日历薄的模样,而后别离布局上部的以后日期和下部的日期表格。实现动态布局之后,再通过脚本来动静生成日期元素,实现一个显示实时日期的动静日历。
一、定义 DOM 构造
dom 的整体构造是一个名为 .container
的容器中蕴含2个元素,.today
是以后日期,.calendar
是日期表格。
<div class="container"> <header class="today"> </header> <main class="calendar"> </main></div>
.today
以后日期局部蕴含2个元素,.month
显示以后月,.day
显示以后日。
<header class="today"> <div class="day">9</div> <div class="month">October</div></header>
.calcendar
日期表格局部蕴含一个表头行 .days
和一个表格 .dates
。表头按英文习惯以周日为每周的第一天;表格共6行,一共显示42天,能够适应任何月份。
表格中的日期通过类名辨别为上月日期 previous-month
、以后日期 current-day
、下月日期 next-month
。
<main class="calendar"> <div class="days"> <span>Sun</span> <span>Mon</span> <span>Tue</span> <span>Wed</span> <span>Thu</span> <span>Fri</span> <span>Sat</span> </div> <div class="dates"> <span class="previous-month">27</span> <span class="previous-month">28</span> <span class="previous-month">29</span> <span class="previous-month">30</span> <span>1</span> <span>2</span> <span>3</span> <span>4</span> <span>5</span> <span>6</span> <span>7</span> <span>8</span> <span class="current-day">9</span> <span>10</span> <span>11</span> <span>12</span> <span>13</span> <span>14</span> <span>15</span> <span>16</span> <span>17</span> <span>18</span> <span>19</span> <span>20</span> <span>21</span> <span>22</span> <span>23</span> <span>24</span> <span>25</span> <span>26</span> <span>27</span> <span>28</span> <span>29</span> <span>30</span> <span>31</span> <span class="next-month">1</span> <span class="next-month">2</span> <span class="next-month">3</span> <span class="next-month">4</span> <span class="next-month">5</span> <span class="next-month">6</span> <span class="next-month">7</span> </div></main>
二、页面整体和日历容器布局
先用线性突变设置页面背景色为灰白过渡色。
body { margin: 0; height: 100vh; background-image: linear-gradient(to bottom, #eee, #ccc);}
设置容器尺寸,用绝对单位 em,并使容器居于页面正中。
为使容器可见,暂为容器填充红色背景。
body { display: flex; align-items: center; justify-content: center;}.container { width: 32em; height: 38em; font-size: 14px; background-color: white;}
成果如下图:
正文掉方才长期定义的 background-color
属性,改为用锐利突变填充,实现上部黄棕色、下部红色的成果。因为黄棕色 sandybrown
是日历主色,前面还会用到,所以把它定义成变量。
再把日历周围设为圆角,底部加双层暗影,模仿多张日历纸叠加的成果。
.container { /* background-color: white; */ --main-color: sandybrown; background-image: linear-gradient(to bottom, var(--main-color) 50%, white 50%); border-radius: 1em; box-shadow: 0 2px 1px rgba(0, 0, 0, 0.2), 0 3px 1px white;}
成果如下图:
接下来绘制一个细节:环扣,它用来连贯日历的上、下两局部。应用2个伪元素来绘制,这样不必显式地减少 DOM 元素,纯用 CSS 实现装璜成果。两个环扣的款式雷同,所以大部分代码是共享的,仅它们所处的地位不同,一个在日历左侧,一个在日历右侧。
.container { position: relative;}.container::before,.container::after { content: ''; position: absolute; width: 0.6em; height: 2.3em; background-color: white; top: calc(50% - 2.3em / 2); border-radius: 0.3em; box-shadow: 0 3px 1px rgba(0, 0, 0, 0.3), 0 -1px 1px rgba(0, 0, 0, 0.2);}.container::before {left: 2em;}.container::after {right: 2em;}
成果如下图:
至此,一个靠近实在场景中的日历的轮廓绘制实现了,接下来布局日历上显示的文字内容。
三、上部以后日期布局
因为整个日历分成高低两局部,所以先令以后日期 .today
占据上部的50%空间,这样表格 .calendar
天然就被挤到下部了。
.today { height: 50%;}
成果如下图:
因为整个日历都应用同一种字体,所以把字体款式定义在容器中,采纳无衬线字体。.today
的布局很简略,用的都是字号、色彩、行间距等根本属性。
.container { font-family: sans-serif;}.today { padding: 3em; box-sizing: border-box; color: white;}.today .day { font-size: 8em; line-height: 1em; font-weight: bold;}.today .month { font-size: 4em; line-height: 1em; text-transform: lowercase;}
成果如下图:
四、下部日期表格布局
表格包含表头和表体两局部,设置好它们的宽度,而后用 flex 布局令其垂直居中排列。
.calendar { padding-top: 3.5em; display: flex; flex-direction: column; align-items: center;}.calendar .days,.calendar .dates { width: 28em;}
表头和表格都是一行7列,这里采纳 grid 布局实现。
.calendar .days,.calendar .dates { display: grid; grid-template-columns: repeat(7, 1fr); line-height: 2em; text-align: center;}
成果如下图:
表格曾经成形,剩下的细节是为文字上色。
表格里一共有5种语义元素:表头、以后日期、本月日期、上月日期、下月日期,这些不同语义的元素都靠 CSS 类名来辨别。表头和以后日期用主色,本月日期用深灰色,上月日期和下月日期用浅灰色。
.calendar .days { color: var(--main-color); font-weight: bold; text-transform: uppercase;}.calendar .dates { color: dimgray;}.calendar .dates .previous-month,.calendar .dates .next-month { color: lightgray;}.calendar .dates .current-day { color: var(--main-color); font-weight: bold;}
成果如下图:
最初,再减少一个鼠标悬停特效,当在日期上悬停气节背景变灰、文字变白,并用 transition
实现平滑过渡。
.calendar .dates span:hover { background-color: lightgray; color: white;}.calendar .dates span { transition: 0.3s;}
至此,整个日历的动态布局全副实现了。
五、动静脚本
程序的入口是一个名为 init
的函数,其中申明了一个 Calendar
类的实例,再调用它的 render()
办法来渲染页面。
function init() { let calendar = new Calendar(new Date()) calendar.render()}window.onload = init
Calendar
类接管一个日期参数,以此来初始化年、月、日数据。render()
办法别离调用了 renderDay()
和 renderMonth()
来渲染以后日期和以后月份。因为 Date
对象返回的月份属性是数字,为了把它显示成英文月份名称,就定义了一个寄存月份名称的数组。
let Calendar = function(date) { let year = date.getFullYear() let month = date.getMonth() let day = date.getDate() function renderDay() { document.querySelector('.day').textContent = day } function renderMonth() { const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] document.querySelector('.month').textContent = MONTHS[month] } this.render = function() { renderDay() renderMonth() }}
接下来要渲染日期表格了。
咱们先引入一个日历库 calendar-dates(github 地址:https://dance2die.github.io/calendar-dates/),它负责计算日期、星期、月份之间的关系,为给定的年月输入对应的日历数据。通过 import
语句导入该库文件。
import CalendarDates from 'https://cdn.jsdelivr.net/npm/calendar-dates@2.6.1/dist/calendardates.esm.js'
留神,对于应用了 import
语句的脚本,在 <script>
标签中需减少 type="module"
属性。
<script src="script.js" type="module"></script>
而后,在 Calendar
类中定义一个 renderDates()
函数来渲染日历列表。如何取得日历能够参考官网文档,这里就不多说了,我感觉有点顺当的是必须用 async/await 的形式来调用。每个日期有 date
和 type
属性,date
就是日期数值,type
有3个值:previous
、current
、next
,别离代表上月、本月、下月,咱们就用这2个属性来解决日期元素的款式。
最初,别忘了要在 render()
里调用一下 renderDates()
函数。
async function renderDates() { const calendarDates = new CalendarDates(); const domList = document.querySelector('.dates') domList.innerHTML = '' for (const meta of await calendarDates.getDates(new Date(year, month))) { let span = document.createElement('span') span.textContent = meta.date span.className = (meta.type == 'current') ? (meta.date == day) ? 'current-day' : '' : meta.type + '-month' domList.append(span) }}this.render = function() { renderDay() renderMonth() renderDates()}
功败垂成!
对于我
张偶,网络笔名 @comehope,20世纪末触网,被 Web 的无穷魅力所俘获,自此始终战斗在 Web 开发第一线。
《前端每日实战》专栏是我近年来实际我的项目式学习办法的笔记,以我的项目驱动学习,展示从灵感闪现到代码实现的残缺过程,亦可作为前端开发的练手习题和开发参考。
拙作《CSS3 艺术》一书于2020年1月由人民邮电出版社出版,全彩印刷,应用100多个活泼好看的实例,系统地分析了 CSS 与视觉效果相干的重要语法,并含有近10小时的视频演示解说。京东/天猫/当当有售。