đôi khi trong 1 project bạn cần tạo ra 1 calendar vẽ bằng javascript và bạn không biết nên bắt đầu từ đâu. Hôm nay mình chia sẽ cách vẽ 1 calendar bằng javascript đơn giản.

tự vẽ một carlendar bằng javascript thuần

2021-01-11 2093 lượt xem
Cao Thị Ngọc

Bài này mình chỉ hướng dẫn bạn tạo 1 carlendar căn bản với Pure JavaScript (không sài thư viện), bạn có thể css thêm, hoặc viết các vấn đề sự kiện thậm chí kế thừa javascript các kiểu con đà điểu nhưng đó là bạn vì mình muốn viết 1 bài dành đơn giản ai đọc cũng sẽ dễ dàng hiểu và sử dụng được. 

Yêu cầu

Đương nhiên bạn cần biết về javascript và chỉ thế thôi 😄 vì bài này mình muốn đơn giản nên không kế thừa hay prototype gì cả nên dễ đọc dễ hiểu mà 

Vài chú ý trước khi vẽ calendar

Lưu ý để vẽ calendar js bạn cần quan tâm đến việc vẽ 1 dom html bằng js. Cú pháp: 

/// tạo thẻ div 
var div = document.createElement("div")
/// tạo class cho div 
div.className = "class-name"

Mình sẽ dùng thẻ div để vẽ từng ô từng cell chứ không dùng thẻ table 😄 Nhưng nếu bạn thích thì bạn cứ dùng table. 

hàm getDay javascript

Phương thức getDay() được dùng để lấy THỨ của một đối tượng ngày tháng.

(THỨ có giá trị 0, 1, 2, 3, 4, 5, 6 tương ứng với CN, T2, T3, T4, T5, T6, T7).

Cú pháp

đối_tượng_ngày_tháng.getDay();
/// mình muốn lấy thứ của ngày đầu tiên của tháng + năm
var first_day = (new Date(this.selectYear, this.selectMonth)).getDay()

Bắt Đầu vẽ Calendar javascript pure

nếu bạn đã có kiến thức về javascript thì đọc đoạn code sau bạn sẽ không hề thấy khó hiểu 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>tự viết calendar</title>
</head>
<body>
    <div id="calendar"></div>


    <script>

        var today        = new Date()
        var currentMonth = today.getMonth()
        var currentYear  = today.getFullYear()
        
        /// tạm thời chưa dùng tới nhưng cứ khởi tạo trước 2 biến LABEL_MONTH, LABEL_DATE
        /// để hàm createBodyCalendar sẽ dùng sau này
        const LABEL_MONTH = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
        const LABEL_DATE  = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]

        /// tạo 2 biến này để xác định tháng và năm mình sẽ lấy làm mốc để vẽ
        var selectYear  = currentYear
        var selectMonth = currentMonth

        //// bắt đầu vẽ calendar
        drawCalendar(selectMonth, selectYear)

        /// function này sẽ tạo dom calendar dựa vào month, year được truyền vào
        function drawCalendar(month, year){
            
            /// tạo thẻ cha bao calendar
            var calendar           = document.createElement("div")
                calendar.className = 'calendar'
            
            /// tạo header 
            var headCalendar = createHeaderCalendar(month, year)
            /// thêm header calendar dom vào calendar chính
            calendar.appendChild(headCalendar)

            /// tạo thân calendar 
            var bodyCalendar = createBodyCalendar(month, year)
            calendar.appendChild(bodyCalendar)

            //// cuối cùng thêm vào dom html của page
            document.getElementById("calendar").appendChild(calendar)
        }

        /**
         * function này để tạo phần header cho calendar có cái nút next và prev
         * để biết next là qua tháng nào năm nào chúng ta cần truyền vào biến month và year
        */
        function createHeaderCalendar(month, year){

            //// tạm thời return ra cái dom có cái text header
            var header           = document.createElement("div")
                header.className = 'calendar__header'
                header.innerText = 'đây là phần header calendar'

            return header
        }
        /**
         * function này để tạo phần cell cho calendar quan trọng nhất
         * để biết chúng ta vẽ calendar cho tháng năm nào cần truyền vào biến month và year
        */
        function createBodyCalendar(month, year){
            //// tạm thời return ra cái dom có cái text header
            var body           = document.createElement("div")
                body.className = 'calendar__body'
                body.innerText = 'đây là phần body calendar'

            return body
        }
    </script>
</body>
</html>

kết quả : 

giải thích 1 chút: 

  • drawCalendar là hàm chạy chính, ban đầu truyền tháng hiện tại và năm hiện tại làm mặc định. 
  • hàm createHeaderCalendar sẽ tạo header có nút next pre và hiện thị ngày tháng năm 
  • hàm createBodyCalendar sẽ tạo phần quan trọng nhất của calendar, các cell sẽ được vẽ dạng dive nên nhớ thêm css nhé

Cập nhật hàm createBodyCalendar để có carlendar body

/**
         * function này để tạo phần cell cho calendar quan trọng nhất
         * để biết chúng ta vẽ calendar cho tháng năm nào cần truyền vào biến month và year
        */
        function createBodyCalendar(month, year){
            
            var body           = document.createElement("div")
                body.className = 'calendar__body'

            // creating all cells
            var dateLoop = 1;
            // tạo cells, 1 tháng có 6 dòng
            for (let i = 0; i < 6; i++) {
                // tạo 1 div bọc các ngày trong 1 tuần lại ( nếu bạn vẽ bằng table thì đây là thẻ tr)
                var row           = document.createElement("div")
                    row.className = "calendar__row"

                // trong 1 tuần luôn có 7 ngày ( nếu bạn vẽ bằng table thì đây là thẻ td )
                for (let j = 0; j < 7; j++) {

                    
                    /// hàm xác định thứ đầu tiên của tháng. 
                    /// cái này rất quan trọng trong việc vẽ calendar
                    /// vì bạn cần xác định calendar này được vẽ vào thứ mấy sau đó bạn for cho hết tháng
                    var firstDay = (new Date(year, month)).getDay()

                    /// tạo cell ( ô )
                    var cell           = document.createElement("div")
                        cell.className = "calendar__cell"

                    if (i === 0 && j < firstDay) {
                        
                        /// ngày của tháng trước ( kiểu như disable đi )
                        cell.innerHTML = "-"
                    }
                    else if (dateLoop > daysInMonth(month, year)) {
                        //// ngày của tháng sau ( kiểu như disable đi )
                        cell.innerHTML = "-"
                    }

                    else {
                        cell.innerText = dateLoop
                        dateLoop++;
                    }

                    row.appendChild(cell)
                }

                // appending each row into calendar body.
                body.appendChild(row)
            }

            return body
        }
        // check how many days in a month
        function daysInMonth(iMonth, iYear) {
            return 32 - new Date(iYear, iMonth, 32).getDate();
        }

Cần Thêm css cho body

<style>

        /* mình giới hạn chiều rộng calendar, bạn xóa max-width này đi nghen  */
        #calendar{
            max-width: 500px;
        }
        /* css cho calendar */
        .calendar{
            display: block
        }
        .calendar__body{
            display: block
        }
        .calendar__row{
            display: flex;
        }
        .calendar__cell{
            flex: 1;
            padding: 10px;
            background-color: #ccc;
            border: 1px solid #f2f2f2;
        }
    </style>

kết quả: 

 

Cập nhật hàm createHeaderCalendar để có carlendar header

/**
         * function này để tạo phần header cho calendar có cái nút next và prev
         * để biết next là qua tháng nào năm nào chúng ta cần truyền vào biến month và year
        */
        function createHeaderCalendar(month, year){

            //// tạm thời return ra cái dom có cái text header
            var header           = document.createElement("div")
                header.className = 'calendar__header'


            /// bắt đầu tạo phần nut next, pre
            var headingTop           = document.createElement("div")
                headingTop.className = "calendar__header-top"
            /// next 
            var prev = document.createElement("button")
            prev.innerText = "prev"
            prev.onclick = function(){

                //// set lại cái biến selectMonth
                /// bạn đang nghĩ chỉ cần trừ 1 lên nhưng cẩn thận 
                /// nếu tháng 12 đem cộng 1 thì ra tháng 13 😄 
                var indexDate   = new Date(selectYear, month - 1, 1 )
                
                selectMonth = indexDate.getMonth()
                selectYear  = indexDate.getFullYear()
                /// cho vẽ lại với 2 giá trị mới 
                drawCalendar(selectMonth, selectYear)
            }
            headingTop.appendChild(prev)

            /// year index 
            var yearIndex           = document.createElement("span")
                yearIndex.innerText = `Tháng ${selectMonth + 1} năm ${selectYear}`
            headingTop.appendChild(yearIndex)

            /// next 
            var next = document.createElement("button")
            next.innerText = "next"
            next.onclick = function(){

                //// set lại cái biến selectMonth
                /// bạn đang nghĩ chỉ cần cộng 1 lên nhưng cẩn thận 
                /// nếu tháng 12 đem cộng 1 thì ra tháng 13 😄 
                var indexDate   = new Date(selectYear, month + 1, 1 )
                
                selectMonth = indexDate.getMonth()
                selectYear  = indexDate.getFullYear()
                /// cho vẽ lại với 2 giá trị mới 
                drawCalendar(selectMonth, selectYear)
            }
            headingTop.appendChild(next)

            header.appendChild(headingTop)

            /// bắt đầu tạo phần label thứ 2, thứ 3, thứ 4, thứ 5 .... 
            var titleDays           = document.createElement("div")
                titleDays.className = "calendar__row calendar__row-head"
            
            for(var head = 0; head < LABEL_DATE.length; head++){

                var thead           = document.createElement("div")
                    thead.className = "calendar__cell calendar__cell-head"
                    thead.innerHTML =  LABEL_DATE[head]

                titleDays.appendChild(thead)
            }
            header.appendChild(titleDays)
            /// kết thúc tạo phần label

            return header
        }

kết quả: nút next prev đã hoạt động: 

 

Cập nhật thêm css cho heading

         .calendar__header-top{

            display: flex;
            justify-content: space-between;
        }
        .calendar__header-top button{
            background-color: #0d6efd;
            color: #fff;
            border: 2px solid #0d6efd;
            text-decoration: null;
            white-space: normal;
            word-wrap: break-word;
            cursor: pointer;
            user-select: none;
            padding: .375rem .75rem;
            margin: .375rem;
            -webkit-border-radius: .125rem;
            border-radius: .125rem;
        }

Full code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>tự viết calendar</title>
    <style>
        /* mình giới hạn chiều rộng calendar, bạn xóa max-width này đi nghen  */
        #calendar{
            max-width: 500px;
        }
        /* css cho calendar */
        .calendar{
            display: block
        }
        .calendar__body{
            display: block
        }
        .calendar__row{
            display: flex;
        }
        .calendar__cell{
            flex: 1;
            padding: 10px;
            background-color: #ccc;
            border: 1px solid #f2f2f2;
        }

        .calendar__header-top{

            display: flex;
            justify-content: space-between;
        }
        .calendar__header-top button{
            background-color: #0d6efd;
            color: #fff;
            border: 2px solid #0d6efd;
            text-decoration: null;
            white-space: normal;
            word-wrap: break-word;
            cursor: pointer;
            user-select: none;
            padding: .375rem .75rem;
            margin: .375rem;
            -webkit-border-radius: .125rem;
            border-radius: .125rem;
        }
    </style>
</head>
<body>
    <div id="calendar"></div>

    <script>

        var today        = new Date()
        var currentMonth = today.getMonth()
        var currentYear  = today.getFullYear()
        
        /// tạm thời chưa dùng tới nhưng cứ khởi tạo trước 2 biến LABEL_MONTH, LABEL_DATE
        /// để hàm createBodyCalendar sẽ dùng sau này
        const LABEL_MONTH = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
        const LABEL_DATE  = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]

        /// tạo 2 biến này để xác định tháng và năm mình sẽ lấy làm mốc để vẽ
        var selectYear  = currentYear
        var selectMonth = currentMonth

        //// bắt đầu vẽ calendar
        drawCalendar(selectMonth, selectYear)

        /// function này sẽ tạo dom calendar dựa vào month, year được truyền vào
        function drawCalendar(month, year){
            
            /// tạo thẻ cha bao calendar
            var calendar           = document.createElement("div")
                calendar.className = 'calendar'
            
            /// tạo header 
            var headCalendar = createHeaderCalendar(month, year)
            /// thêm header calendar dom vào calendar chính
            calendar.appendChild(headCalendar)

            /// tạo thân calendar 
            var bodyCalendar = createBodyCalendar(month, year)
            calendar.appendChild(bodyCalendar)

            //// cuối cùng thêm vào dom html của page
            document.getElementById("calendar").innerHTML = ''
            document.getElementById("calendar").appendChild(calendar)
        }

        /**
         * function này để tạo phần header cho calendar có cái nút next và prev
         * để biết next là qua tháng nào năm nào chúng ta cần truyền vào biến month và year
        */
        function createHeaderCalendar(month, year){

            //// tạm thời return ra cái dom có cái text header
            var header           = document.createElement("div")
                header.className = 'calendar__header'


            /// bắt đầu tạo phần nut next, pre
            var headingTop           = document.createElement("div")
                headingTop.className = "calendar__header-top"
            /// next 
            var prev = document.createElement("button")
            prev.innerText = "prev"
            prev.onclick = function(){

                //// set lại cái biến selectMonth
                /// bạn đang nghĩ chỉ cần trừ 1 lên nhưng cẩn thận 
                /// nếu tháng 12 đem cộng 1 thì ra tháng 13 😄 
                var indexDate   = new Date(selectYear, month - 1, 1 )
                
                selectMonth = indexDate.getMonth()
                selectYear  = indexDate.getFullYear()
                /// cho vẽ lại với 2 giá trị mới 
                drawCalendar(selectMonth, selectYear)
            }
            headingTop.appendChild(prev)

            /// year index 
            var yearIndex           = document.createElement("span")
                yearIndex.innerText = `Tháng ${selectMonth + 1} năm ${selectYear}`
            headingTop.appendChild(yearIndex)

            /// next 
            var next = document.createElement("button")
            next.innerText = "next"
            next.onclick = function(){

                //// set lại cái biến selectMonth
                /// bạn đang nghĩ chỉ cần cộng 1 lên nhưng cẩn thận 
                /// nếu tháng 12 đem cộng 1 thì ra tháng 13 😄 
                var indexDate   = new Date(selectYear, month + 1, 1 )
                
                selectMonth = indexDate.getMonth()
                selectYear  = indexDate.getFullYear()
                /// cho vẽ lại với 2 giá trị mới 
                drawCalendar(selectMonth, selectYear)
            }
            headingTop.appendChild(next)

            header.appendChild(headingTop)

            /// bắt đầu tạo phần label thứ 2, thứ 3, thứ 4, thứ 5 .... 
            var titleDays           = document.createElement("div")
                titleDays.className = "calendar__row calendar__row-head"
            
            for(var head = 0; head < LABEL_DATE.length; head++){

                var thead           = document.createElement("div")
                    thead.className = "calendar__cell calendar__cell-head"
                    thead.innerHTML =  LABEL_DATE[head]

                titleDays.appendChild(thead)
            }
            header.appendChild(titleDays)
            /// kết thúc tạo phần label

            return header
        }
        /**
         * function này để tạo phần cell cho calendar quan trọng nhất
         * để biết chúng ta vẽ calendar cho tháng năm nào cần truyền vào biến month và year
        */
        function createBodyCalendar(month, year){
            
            var body           = document.createElement("div")
                body.className = 'calendar__body'

            // creating all cells
            var dateLoop = 1;
            // tạo cells, 1 tháng có 6 dòng
            for (let i = 0; i < 6; i++) {
                // tạo 1 div bọc các ngày trong 1 tuần lại ( nếu bạn vẽ bằng table thì đây là thẻ tr)
                var row           = document.createElement("div")
                    row.className = "calendar__row"

                // trong 1 tuần luôn có 7 ngày ( nếu bạn vẽ bằng table thì đây là thẻ td )
                for (let j = 0; j < 7; j++) {

                    
                    /// hàm xác định thứ đầu tiên của tháng. 
                    /// cái này rất quan trọng trong việc vẽ calendar
                    /// vì bạn cần xác định calendar này được vẽ vào thứ mấy sau đó bạn for cho hết tháng
                    var firstDay = (new Date(year, month)).getDay()

                    /// tạo cell ( ô )
                    var cell           = document.createElement("div")
                        cell.className = "calendar__cell"

                    if (i === 0 && j < firstDay) {
                        
                        /// ngày của tháng trước ( kiểu như disable đi )
                        cell.innerHTML = "-"
                    }
                    else if (dateLoop > daysInMonth(month, year)) {
                        //// ngày của tháng sau ( kiểu như disable đi )
                        cell.innerHTML = "-"
                    }

                    else {
                        cell.innerText = dateLoop
                        dateLoop++;
                    }

                    row.appendChild(cell)
                }

                // appending each row into calendar body.
                body.appendChild(row)
            }

            return body
        }
        // check how many days in a month code from https://dzone.com/articles/determining-number-days-month
        function daysInMonth(iMonth, iYear) {
            return 32 - new Date(iYear, iMonth, 32).getDate();
        }
    </script>
</body>
</html>

Kết quả và ghi chú nếu muốn phát triển thêm

thành quả

 

ghi chú

trong function createBodyCalendar, các ngày của tháng trước chưa được tính toán ra, và tương tự ngày của tháng sau cũng chưa đc tính ra. bạn nên tính toán để show ra cái ngày đó để hoàn thiện hơn. nếu bạn muốn tìm hiểu kỹ hơn thì theo dõi phần 2 của việc tạo 1 calendar nha, vẽ calendar bằng javascript pure core phần 2 - Code optimization . Cảm ơn Bạn quan tâm bài viết