Recently, I am working on a demand similar to a class schedule. I need to make a calendar support function and display. By the way, I will study how to develop a calendar component.

update

  • 2.23 Fixed the bug of rendering more than one line in February 2026. The solution is to make the February calendar special. New Date(year, month+1, 0).getDay() === 6 will not render any later dates.
  • After work update a ha, more scientific logic
    // if (total_calendar_list.length > 35) {
    // nextNum = 42 - total_calendar_list.length;
    // } else {
    // nextNum = 35 - total_calendar_list.length;
    // }
    
    // if (month === 1 && new Date(year, month, 0).getDay() === 6) {
    // nextNum = 0
    // }
    
    nextNum = 6 - new Date(year, month+1.0).getDay()
    Copy the code

This paper mainly involves the following contents:

  • How to develop a calendar skin?
  • How to calculate year month day?
  • How to develop calendar-related features?
  • Summary &DEMO source code

How to develop a calendar skin?

Layer separation, block independent

Before sorting through the calendar logic, I’d like to note a few questions related to the style of the calendar:

The following is an adaptive transformation based on VW based on pX2REM model. To put it simply, in the case that our design is twice the size of iPhone8, calculate the ratio of the width of an element to 375(the maximum width of iPhone8) and then multiply it by 100VW to get the vw value of the element. Because VW is a percentage unit relative to the screen, we can achieve the desired adaptive effect. In different screens, the same element is displayed in the same proportion.

// The Rem layout is used
@function pxWithVw($n) {
  @return 100vw * $n / 375;
}
// Specify the limit width to avoid a bad look on PC
@function pxWithVwMax($n) {
  @return 480px * $n / 375;
}
Copy the code

With the above SCSS function, we can basically not worry about the screen adaptation problem, can enjoy the style. As for the style of the calendar, it is ok to say that it is complicated, we just need to divide the hierarchy well before we do it.

As shown above, each box represents a layer of elements, resulting in a layout that looks like this

<! -- The outermost div defines the entire calendar width and some rounded shadows -->
<div class="calendar">
  <! --header is the content in the green box above, including the last month switch and the calendar title-->
  <div class="calendar__header"></div>
  <! As the name implies, main is the core content of the calendar, which is the display area of the date -->
  <div class="calendar__main">
    <! -- Monday ~ Sunday display header, list render fixed 7 blocks -->
    <div class="main__block-head"></div>
    <! -- Date display area for the corresponding month, list rendering -->
    <div class="main__block"></div>
  </div>
</div>
Copy the code

The layout of calendar__main may seem strange to you after you read it, and why the fixed display heads are not separated, but when you actually write about it, you will realize that this is not necessary.

Since we use pxWithVw to specify the width of calendar__main and the width of each block, we can ensure that every seven elements will fill our line. We can use justify-content: space-around to ensure that the gaps of each element are consistent.

All right, so that’s separation, what is block independence?

It mainly refers to the display blocks of dates. We are independent of each block, so that when rendering, we can easily decide what style to display it, or what events to bind to it, which provides convenience for us to accurately control the display of each date.

How to calculate year month day?

The contents of this section can be summed up in one sentence —

We just need to know what day of the week the first of the month is to render the entire calendar

As for the calculation of year, month and day, I have two models. One is to calculate only the date of the month, and the other is to calculate all the dates of the whole year. I’d like to focus on the first in this post, but if you want to learn more about the second, check out this calendar demo on my Github site.

Let’s look at the picture first. There are 28 days in February, the first of which is Thursday. Does that mean we just render 28 ‘main__blocks’ in order from Thursday? In fact, the key is how to position our 1st to Thursday, as long as this can be accurately positioned, our calendar will naturally come out.

// Define the number of days in each month. In leap years, the second month is 29
// year=2018; month=1(js--month=0~11)
let daysInMonth = [31.28.31.30.31.30.31.31.30.31.30.31];
if ((year % 4= = =0 && year % 100! = =0) || year % 400= = =0) {
    daysInMonth[1] = 29;
}
// What day of the week is the first day of the designated month
let targetDay = new Date(year, month, 1).getDay();
// The list to render in calendar__main
let total_calendar_list = [];
let preNum = targetDay;
// First of all, our date is (day -6). This order is (0- 6).
[total_calendar_list] [total_calendar_list] [total_calendar_list] [total_calendar_list] [total_calendar_list] [total_calendar_list] [total_calendar_list] [total_calendar_list] [total_calendar_list] [total_calendar_list
if (targetDay > 0) {
    for (let i = 0; i < preNum; i++) {
        let obj = {
            type: "pre".content: ""}; total_calendar_list.push(obj); }}Copy the code

In this way, the position of number 1 is automatically the Thursday we need, and we just need to render in order. Here is the rest of the date array to fill in, and then return it for our View layer to use.

for (let i = 0; i < daysInMonth[month]; i++) {
    let obj = {
        type: "normal".content: i + 1
    };
    total_calendar_list.push(obj);
}
nextNum = 6 - new Date(year, month+1.0).getDay()
Type =pre
for (let i = 0; i < nextNum; i++) {
    let obj = {
        type: "next".content: ""
    };
    total_calendar_list.push(obj);
}
return total_calendar_list;
Copy the code

How to develop calendar-related features?

How to choose last month or next month?

data() {
    return {
        // ...
        selectedYear: new Date().getFullYear(),
        selectedMonth: new Date().getMonth(),
        selectedDate: new Date().getDate()
    };
}

handlePreMonth() {
    if (this.selectedMonth === 0) {
        this.selectedYear = this.selectedYear - 1
        this.selectedMonth = 11
        this.selectedDate = 1
    } else {
        this.selectedMonth = this.selectedMonth - 1
        this.selectedDate = 1
    }
}

handleNextMonth() {
    if (this.selectedMonth === 11) {
        this.selectedYear = this.selectedYear + 1
        this.selectedMonth = 0
        this.selectedDate = 1
    } else {
        this.selectedMonth = this.selectedMonth + 1
        this.selectedDate = 1}}Copy the code

It’s as simple as that, but the important thing to note is the time transition of the New Year, we need to change the year as well as the month to render the correct date.

You may wonder, how can you change the month or year without recalculating the date? In fact, there is computation. I don’t know if you still remember, VUE is data-driven change, we only need to pay attention to the change of data, vUE will help us solve other things.

If I pick a day?

handleDayClick(item) {
    if (item.type === 'normal') {
        // do anything...
        this.selectedDate = Number(item.content)
    }
}
Copy the code

When I render the list, I bind the click event to each block. The advantage of this is that it is very convenient to call. When you click on each block, you can get the contents of the block and do anything you like

We can also bind an event listener to the outer parent element, and handle each block’s click event through the event stream

conclusion

A mobile calendar seems to have been completed without a scratch. Generally speaking, the calendar is still a style aspect. It does not require too much logic, but it requires a certain understanding of flexbox layout to quickly build the skeleton of the calendar. However, I think using Flex is a little more efficient.

Vue written calendar DEMO- Github

By the way, why did you want to write a calendar? This calendar component was originally written as a react component and then changed to a Vue component to try it out in vue. In react, it’s the same thing, but I’ll separate the date blocks into stateless components, which makes it look better: