关于html:前端每日实战第179号作品撕日历

8次阅读

共计 4958 个字符,预计需要花费 13 分钟才能阅读完成。

成果预览

按下右侧的“点击预览”按钮能够在以后页面预览,点击链接能够全屏预览。

https://codepen.io/comehope/pen/qBLRbBM

源代码下载

每日前端实战系列的全副源代码请从 github 下载:

https://github.com/comehope/front-end-daily-challenges

代码解读

这个撕日历的我的项目尽管代码量不大,但外面有一些精妙的技巧,值得咱们学习。

整个作品咱们分成三个步骤开发,先进行动态布局,而后实现动静更换日历页,最初退出动画成果。

一、动态布局

DOM 构造是一个 .calendar 容器,其中蕴含一个示意日历页的 .page 元素,.page 里包含示意月份的 .month 元素,示意日期的 .day 元素,示意星期的 .day-name 元素,示意年的 .year 元素。

回忆一下生存中的日历,一本日历是蕴含很多日历页的,在这里也是同样的,前面你会看到,当撕掉一个 .page 元素之后,就会减少一个新的 .page 元素。当初咱们只解决布局,所以临时 DOM 中只排版一页就够了:

<div class="calendar">
    <div class="page">
        <p class="month">November</p>
        <p class="day">1</p>
        <p class="day-name">Monday</p>
        <p class="year">2021</p>
    </div>
</div>

设置 body 的子元素,也就是 .calendar 元素居中,同时设置深色背景:

body {
    margin: 0;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: #222;
}

画出日历的轮廓,用 background-image 的线性突变函数,把 .calendar 容器分成上、中、下三局部,上局部是砖红色的日历顶部,中部是浅黄色的日历页,下部是深卡其色的日历底部:

.calendar {
    width: 152px;
    height: 218px;
    background-image: 
        linear-gradient(
            firebrick 0,
            firebrick 45px,
            lightgoldenrodyellow 45px,
            lightgoldenrodyellow 208px,
            darkkhaki 208px,
            darkkhaki 218px
        );
    position: relative;
}

以后成果如下图所示:

而后,再用 ::before 伪元素画 2 个圆点,作为日历顶部的 2 个铆钉。

.calendar::before {
    content: '';
    position: absolute;
    height: 45px;
    width: 100%;
    background-color: firebrick;
    background-image: 
        radial-gradient(circle at 40px 20px, orange 5px, transparent 5px),
        radial-gradient(circle at 110px 20px, orange 5px, transparent 5px);
    z-index: 3;
}

以后成果如下图所示:

接下来布局文字:

.page {
    margin-top: 45px;
    width: 100%;
    height: 163px;
    box-sizing: border-box;
    padding: 16px 0 10px 0;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    position: absolute;
    background-color: lightgoldenrodyellow;
}

.page p {
    color: darkslategray;
    margin: 0;
    line-height: 1em;
    text-align: center;
    text-transform: uppercase;
    letter-spacing: 1px;
    pointer-events: none;
    user-select: none;
}

.page p.month,
.page p.day-name {
    font-weight: bold;
    font-size: 1.1em;
}

.page p.day {
    font-weight: bold;
    font-size: 4em;
    margin-bottom: 10px;
}

整个日历的动态视觉效果曾经实现了,如下图所示:

接下来钻研如何绘制出撕纸时的锯齿成果。再回忆一下理论生存中的日历,当撕日历时,日历页是从顶部的 2 颗铆钉上面被撕去的,所以锯齿的地位大概在日历顶部与中部联合的地位,但而且为了好看,要把锯齿暗藏在顶部上面,那为了绘制锯齿,就要先把日历顶部暗藏起来。

.calendar::before {display: none;}

锯齿绘制在伪元素 .page::before 中。锯齿是用 background-image 的线性突变函数绘制的,原理是在 10px * 10px 的区域内用 2 个歪斜 45 度的三角组合成一个“齿”,在元素上平铺的多个“齿”相连就造成了锯齿成果。另外还有 1 个细节,用 filter: drop-shadow() 函数给锯齿加了暗影,使它更真切。代码如下:

.page::before {
    content: '';
    width: 100%;
    height: 10px;
    position: absolute;
    top: -5px;
    background-image: 
        linear-gradient(-45deg, lightgoldenrodyellow 50%, transparent 50%),
        linear-gradient(45deg, lightgoldenrodyellow 50%, transparent 50%);
    background-repeat: repeat-x;
    background-size: 10px 10px;
    filter: drop-shadow(0 -5px 1px rgba(0, 0, 0, 0.1));
    z-index: 2;
}

成果如下图所示:

别忘了再把方才暗藏的日历顶部再显示进去,遮盖住锯齿。

.calendar::before {display: block;}

留神后面代码中应用的 z-index 属性,通过它设置锯齿的图层在日历顶部之下。这里应用了值 23,而值 1 提前预留给日历页 .page 应用,前面实现动画时会细说。

.calendar::before {z-index: 3;}

.page::before {z-index: 2;}

至此,动态布局实现,接下来解决动态数据。

二、动态数据

JavaScript 原生的 Date 对象不易用,所以咱们引入第三方的 dayjs 库,不便接下来做日期运算。

<script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.9/dayjs.min.js"></script>

接下来写 JavaScript 代码。

定义变量 calendardatecalendar 是对 DOM 元素 .calendar 的援用,date 存储日历上的以后日期。

创立页面初始化函数 init(),对 calendardate 函数进行初始化,先清空 calendar 元素,再调用 addPage() 函数动静增加一个 .page 元素:

let calendar, date;

function addPage(d) {//todo}

function init() {calendar = document.querySelector('.calendar')
    calendar.innerHTML = ''
    date = dayjs()
    addPage(date)
}

window.onload = init

addPage() 函数的实现如下,留神,咱们为 .page 减少了一个 click 事件的监听器,监听函数是 tear(),当点击时会把这张日历页撕下:

function addPage(d) {let newPage = document.createElement('div')
    newPage.classList.add('page')
    newPage.innerHTML = `
        <p class="month">${d.format('MMMM')}</p>
        <p class="day">${d.format('D')}</p>
        <p class="day-name">${d.format('dddd')}</p>
        <p class="year">${d.format('YYYY')}</p>
    `
    newPage.addEventListener('click', tear)
    calendar.appendChild(newPage)
}

function tear(e) {//todo}

tear() 函数的实现如下,它把以后日历页从 .calendar 容器中删除掉,
而后再增加一个新的 .page 元素:

function tear(e) {
    let page = e.target
    calendar.removeChild(page)
    date = date.add(1, 'day')
    addPage(date);
}

至此,实现了点击更换日历页的性能。
此时,点击日历页时,以后的日历页会隐没,替换为第二天的日历页。

三、动画成果

终于到最初制作动画成果了,这是本我的项目最简单的中央。

咱们把动画代码写在 .page 元素的 tear 类外面。首先看动画关键帧的代码,定义了 3 组关键帧:tear-down 用于使日历页向下方挪动 200pxtear-fade 用于使日历页渐隐隐没,tear-shake 模仿日历页被撕下时的稍稍歪斜。动画总时长 1 秒钟。

.page.tear {
    position: absolute;
    z-index: 1;
    pointer-events: none;
    transform-origin: top left;
    animation: 1s linear forwards;
    animation-name: tear-down, tear-fade, tear-shake;
}

@keyframes tear-down {from, 20% {top: 0;}
    to {top: 200px;}
}

@keyframes tear-fade {from, 70% {filter: opacity(1);}
    to {filter: opacity(0);}
}

@keyframes tear-shake {from, to {transform: rotate(0deg);}
    20% {transform: rotate(10deg);}
}

而后,批改 JavaScript 的 tear() 函数,在删除以后日历页之前,先为以后日历页减少 .tear 的 CSS 属性,使它运行动画成果。同时,要在动画完结之后再删除日历页,所以定义一个 waitMoment() 函数,它是一个 Promise,使删除操作提早 1 秒执行。

function tear(e) {
    let page = e.target
    page.classList.add('tear')
    waitMoment().then(() => {calendar.removeChild(page)
    })
    date = date.add(1, 'day')
    addPage(date);
}

function waitMoment() {
    const interval = 1000
    return new Promise(function(resolve, reject) {setTimeout(resolve, interval);
    })
}

至此,动画成果制作实现,成果如下图所示:

功败垂成!

对于作者

张偶,网络笔名 comehope,20 世纪末触网,被 Web 的无穷魅力所俘获,自此始终战斗在 Web 开发第一线。

《前端每日实战》专栏是我近年来实际我的项目式学习的笔记,以我的项目驱动学习,展示从灵感闪现到代码实现的残缺过程,亦可作为前端开发的练手习题和开发参考。

拙作《CSS3 艺术》一书已由人民邮电出版社出版,全彩印刷,用 100 多个活泼好看的实例,系统地分析了 CSS 与视觉效果相干的重要语法,并含有近 10 小时的视频演示解说。京东 / 天猫 / 当当有售。

正文完
 0