JavaScript+HTML5+CSS实现精美好看的Todolist清单(附完整源码)

1.作品展示

 这里的沙漏是真实随时间变化而变化的,当时间为零,下方的沙漏将会被填满,这里其实是想找一个现实中的沙漏,当网上好多素材都是收费的,所以就做了个简易版。

 2.功能介绍

1.标题

大家应该能看到我上面两张图的颜色是不同,其实我是用了随机颜色来展示这个标题的,下面是实现这个功能的JavaScript代码,这里主要用十六进制颜色表示,通过随机数获得随机的颜色

//设置随机颜色标题
function getRandomColor() {
  const letters = '0123456789ABCDEF'
  let color = '#'
  for (let i = 0; i < 6; i++) {
    color += letters[Math.floor(Math.random() * 16)];
  }
  return color
}

function updateRandomColor() {
  const title = document.querySelector('.title')
  const color1 = getRandomColor();
  const color2 = getRandomColor();
  const color3 = getRandomColor();
  title.style.color = getRandomColor()
  title.style.backgroundImage = `linear-gradient(to right, ${color1}, ${color2}, ${color3})`
}

 这里是css代码

@keyframes colorChange {
  0% {
    background-position: 0% 50%;
  }
  70% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0% 50%;
  }
}
.title {
  font-size: 60px;
  margin-top: 50px;
  text-align: center;
  background-image: linear-gradient(to right, #ff0000, #b9e3f0, #0000ff);
  background-size: 200% 200%;
  animation: colorChange 8s ease infinite;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

2.时间显示

可以看到我这个清单的坐上角有一个时间显示,这个是实时获取当时的时间,不断更新的,代码也是挺简单的

//表单日期
function getdate() {
  const currentDate = new Date()
  //const year = currentDate.getFullYear()
  const month = String(currentDate.getMonth() + 1).padStart(2, '0')
  const day = String(currentDate.getDate()).padStart(2, '0')
  const hours = String(currentDate.getHours()).padStart(2, '0')
  const minutes = String(currentDate.getMinutes()).padStart(2, '0')
  //const seconds = String(currentDate.getSeconds()).padStart(2, '0')
  return `${month}/${day} ${hours}:${minutes}`
}
setInterval(function () {
  document.querySelector('.date').innerHTML = new Date().toLocaleString()
}, 1000)

3.任务添加逻辑

这里的任务添加逻辑主要通过获取你输入在文本框的数据(这里有非空判断和去除前后空格),然后localStorage上传但本地浏览器,保存下来,当你刷新页面是仍然存在

//添加数据到本地
console.log(new Date().toLocaleString())
const info = document.querySelector('.info')
const task = document.querySelector('.task')
const error_message = document.querySelector('.error_message')
const data = JSON.parse(localStorage.getItem('data')) || []

//数据提交
const taskList = document.querySelector('.task_list')

renderTask()

info.addEventListener('submit', function (e) {
  e.preventDefault()
  //去除前后空格
  let taskValue = task.value.trim()
  if (taskValue) {
    error_message.textContent = ''
    data.push(
      {
        value: taskValue,
        time: getdate(),
        actionTime: '0',
        status: 'fasle'
      }
    )
    while (taskList.firstChild) {
      taskList.removeChild(taskList.firstChild)
    }
    renderTask()
  } else {
    error_message.textContent = '请输入任务内容'
  }
  saveTasksStorage()
  this.reset()
})
//添加数据
function addTask(allTask) {
  const taskLine = document.createElement('li')
  taskLine.classList.add('taskList')
  const taskRadio = document.createElement('input')
  taskRadio.type = 'checkbox';
  taskRadio.classList.add('checkbox')

  //复选框操作 
  taskRadio.addEventListener('change', () => {
    const taskIndex = Array.from(taskLine.parentNode.children).indexOf(taskLine);
    if (taskRadio.checked) {
      taskLine.classList.add('completed')
      taskLine.classList.add('aaaa')
      taskRadio.style.pointerEvents = 'auto'
      data[taskIndex].status = 'true'
      checkPause()
      resetTimer()
    } else {
      taskLine.classList.remove('completed')
      // 取消之前设置的定时器
      taskLine.classList.remove('aaaa')
      data[taskIndex].status = 'false'
      checkStart()
      //clearTimeout(taskLine.dataset.timeoutId)
    }
    saveTasksStorage()
  })

  //文本显示和编辑
  const taskValue = document.createElement('span')
  taskValue.textContent = allTask.value
  taskValue.classList.add('task_vlaue')
  taskValue.setAttribute('contenteditable', 'true')
  taskValue.setAttribute('spellcheck', 'false')
  taskValue.addEventListener('input', () => {
    taskValue.addEventListener('keydown', (event) => {
      // 检查是否按下了 Enter 键
      if (event.key === 'Enter') {
        // 阻止默认的换行行为
        event.preventDefault()
        // 获取更改后的内容
        const updatedContent = taskValue.textContent.trim()
        const index = Array.from(taskLine.parentNode.children).indexOf(taskLine)
        // 将更改后的内容保存到本地存储
        data[index].value = updatedContent
        if (updatedContent === '') {
          taskValue.textContent = '无标题'
        } else {
          // 将更改后的内容保存到本地存储
          localStorage.setItem('data', JSON.stringify(data))
        }
        // 触发 blur 事件,使编辑状态结束
        taskValue.blur();
      }
    })
  })

  const taskTime = document.createElement('span')
  taskTime.textContent = allTask.time
  taskTime.classList.add('task_time')

  const taskFunction = document.createElement('div')
  taskFunction.classList.add('taskFunction')

  //推迟
  const template1 = document.querySelector('.pattern1')
  const taskRemind = template1.content.cloneNode(true);

  //关闭按钮
  const taskDelete = document.createElement('div')
  taskDelete.classList.add('taskDelete')
  taskDelete.textContent = '关闭'
  taskDelete.addEventListener('click', () => {
    if (data.length <= +localStorage.getItem('indexNow')) {
      localStorage.setItem('indexNow', '0')
      saveTasksStorage()
    }
    deletetask(taskLine);
  });

  taskFunction.appendChild(taskRemind)
  taskFunction.appendChild(taskDelete)
  taskLine.appendChild(taskRadio)
  taskLine.appendChild(taskValue)
  taskLine.appendChild(taskTime)
  taskLine.appendChild(taskFunction)
  selecttime(taskLine)
  return taskLine
}

4.渲染数据

这个主要的实现逻辑是通过获取你储存在浏览器的数据,然后遍历渲染的你的页面上,添加新的数据时并实时更新

//检测输入框上是否有数据
task.addEventListener('input', () => {
  if (error_message.textContent !== '') {
    error_message.textContent = ''
  }
})
//保存数据到本地
function saveTasksStorage() {
  localStorage.setItem('data', JSON.stringify(data))
}

//渲染数据
function renderTask() {
  while (taskList.firstChild) {
    taskList.removeChild(taskList.firstChild)
  }
  for (let i = 0; i < data.length; i++) {
    const task = data[i]
    const taskLine = addTask(task)
    const checked = taskLine.querySelector('.checkbox')
    if (task.status === 'true') {
      taskLine.classList.add('completed')
      taskLine.classList.add('aaaa')
      checked.checked = 'true'
      checked.style.pointerEvents = 'auto'
      taskList.appendChild(taskLine)
    } else {
      taskList.appendChild(taskLine)
    }
  }
}

5.删除数据

删除数据主要逻辑是当你点击你想要删除的一个任务的关闭按钮时,获取你点击这行的索引,在你储存data对象数组删除这一行数据,然后再上传到本地,再去重新渲染页面。

 

//删出数据
function deletetask(taskLine) {
  const taskIndex = Array.from(taskList.childNodes).indexOf(taskLine)
  console.log(taskIndex)
  data[taskIndex].actionTime = '0'
  data.splice(taskIndex, 1);
  saveTasksStorage()
  //taskLine.remove();
  while (taskList.firstChild) {
    taskList.removeChild(taskList.firstChild);
  }
  resetTimer()
  renderTask();
}

 6.推迟时间和沙漏时间显示

这个功能是整个项目最难的地方,因为他要获取你点击的时间,并且还要考虑你当时在哪一行数据操作,以及当你在去点击其他任务时时间沙漏的变化,还有就是当你刷新页面是这个沙漏倒计时是不停止的,只有当你点击展厅或者重置按钮或者你点击复选框完成这项任务,时间才会暂停,这里的沙漏变化也是一个难点,需要根据倒计时还有多少时间就更新你沙漏状态。

//获取你选择的执行时间
function selecttime(taskLine) {
  //const func = taskLine.querySelector('.taskFunction')
  // const thirdChild = taskLine.children[2];
  taskLine.children[3].addEventListener('change', function (event) {
    // taskLine.style.backgroundColor = ''
    const delaySelectValue = +(event.target.value * 60)
    const delayIndex = Array.from(taskList.childNodes).indexOf(taskLine)
    console.log(delayIndex);
    data[delayIndex].actionTime = +data[delayIndex].actionTime + delaySelectValue
    // let indexNow = delayIndex
    //当前你的时间记录行的索引
    localStorage.setItem('indexNow', JSON.stringify(delayIndex))
    //更新数据到本地
    saveTasksStorage()
    //执行你所选的倒计时时间
    startTimer(data[delayIndex].actionTime, taskLine)

    const childrenLine = document.querySelectorAll('.taskList')
    childrenLine.forEach(child => {
      childrenLine.forEach(c => c.classList.remove('hintColor'));
      taskLine.classList.add('hintColor')
    });
    // 为当前点击的子元素添加 'active' 类
    //taskLine.classList.add('hintColor');
    //taskLine.classList.add('hintColor')
  })
}
//刷新页面时显示倒计时正在进行哪一行
(function () {
  const childrenLine = document.querySelectorAll('.taskList')
  if (childrenLine.length > 0) {
    childrenLine[+localStorage.getItem('indexNow')].classList.add('hintColor')
  }
})()

//时间沙漏

let timer
let totalSeconds = 0
let elapsedSeconds = 0
let isRunning = false

const topSand = document.getElementById('topSand')
const bottomSand = document.getElementById('bottomSand')
const timerDisplay = document.getElementById('timer')
const pauseBtn = document.getElementById('pauseBtn')

//当页面刷新时继续倒计时上次的
if (data.length > 0) {
  startTimer2(data[+localStorage.getItem('indexNow')].actionTime, data[+localStorage.getItem('indexNow')])
}

function startTimer2(minutes, taskLine) {
  resetTimer()
  if (minutes === null) {
    minutes = 0
  }
  totalSeconds = minutes
  elapsedSeconds = 0
  if (data[+localStorage.getItem('indexNow')].status === 'false') {
    isRunning = true
    pauseBtn.disabled = false
    updateTimer()

    timer = setInterval(() => {
      if (isRunning) {
        updateTimer()
        elapsedSeconds++
        if (elapsedSeconds > totalSeconds) {
          pauseBtn.disabled = true
          //时间到去除这项任务
          setTimeout(function () {
            //deletetask(taskLine)
          }, 2000)
          clearInterval(timer)
        }
      }
    }, 1000)

  }
}


function startTimer(minutes, taskLine) {
  resetTimer()
  if (minutes === null) {
    minutes = 0
  }
  totalSeconds = minutes
  elapsedSeconds = 0
  isRunning = true
  pauseBtn.disabled = false
  updateTimer()
  timer = setInterval(() => {
    if (isRunning) {
      updateTimer()
      elapsedSeconds++
      if (elapsedSeconds > totalSeconds) {
        pauseBtn.disabled = true
        //时间到去除这项任务
        setTimeout(function () {
          //deletetask(taskLine)
        }, 2000)
        clearInterval(timer)
      }
    }
  }, 1000)
}
//更新时间
function updateTimer() {
  const progress = elapsedSeconds / totalSeconds
  topSand.style.width = `${200 * (1 - progress)}px`
  topSand.style.height = `${150 * (1 - progress)}px`
  bottomSand.style.width = `${200 * progress}px`
  bottomSand.style.height = `${150 * progress}px`

  const remainingSeconds = totalSeconds - elapsedSeconds
  const minutes = Math.floor(remainingSeconds / 60)
  const seconds = remainingSeconds % 60
  timerDisplay.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`

  //跟新本地数据时间
  data[+localStorage.getItem('indexNow')].actionTime = remainingSeconds
  saveTasksStorage()
}

//时间的暂停
function pauseResumeTimer() {
  isRunning = !isRunning;
  pauseBtn.textContent = isRunning ? '暂停' : '继续'
}

//当结束任务是的时间状态

function checkPause() {
  isRunning = false;
  pauseBtn.disabled = true
}

function checkStart() {
  isRunning = false
  pauseBtn.disabled = false
  pauseBtn.textContent = isRunning ? '暂停' : '继续'
}

//重置沙漏状态
function resetTimer() {
  clearInterval(timer)
  elapsedSeconds = 0
  isRunning = false
  topSand.style.width = '200px'
  topSand.style.height = '150px'
  bottomSand.style.width = '0px'
  bottomSand.style.height = '0px'
  timerDisplay.textContent = '00:00'
  pauseBtn.textContent = '暂停'
  pauseBtn.disabled = true
}

//重置并清空时间
const resetButton = document.querySelector('.test111')
resetButton.addEventListener('click', function () {
  clearInterval(timer)
  data[localStorage.getItem('indexNow')].actionTime = '0'
  localStorage.setItem('data', JSON.stringify(data))
})

7.数据更改

这里的数据文字更改是可以直接选择在你渲染好的数据上修改的,主要逻辑也是文本哪里添加一个键盘监听,当你修改好数据按下enter键就会完成修改,也是通过先更改定义的对象数组data里的数据然后再上传本地数据,最后重新渲染。

//文本显示和编辑
  const taskValue = document.createElement('span')
  taskValue.textContent = allTask.value
  taskValue.classList.add('task_vlaue')
  taskValue.setAttribute('contenteditable', 'true')
  taskValue.setAttribute('spellcheck', 'false')
  taskValue.addEventListener('input', () => {
    taskValue.addEventListener('keydown', (event) => {
      // 检查是否按下了 Enter 键
      if (event.key === 'Enter') {
        // 阻止默认的换行行为
        event.preventDefault()
        // 获取更改后的内容
        const updatedContent = taskValue.textContent.trim()
        const index = Array.from(taskLine.parentNode.children).indexOf(taskLine)
        // 将更改后的内容保存到本地存储
        data[index].value = updatedContent
        if (updatedContent === '') {
          taskValue.textContent = '无标题'
        } else {
          // 将更改后的内容保存到本地存储
          localStorage.setItem('data', JSON.stringify(data))
        }
        // 触发 blur 事件,使编辑状态结束
        taskValue.blur();
      }
    })
  })

3.完整代码展示

1.HTML

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="./index.css">
  <link rel="stylesheet" href="./base.css">
  <script async src="./JS.js"></script>
</head>

<body>
  <video class="bg_video" src="./images/wallpaper-dynamic_wallpaper-dynamic-357.mp4" autoplay loop muted></video>
  <h1 class="title">todoList</h1>
  <div class="subject">
    <div class="subject_left">

      <!-- 头部标题 -->
      <div class="sb_title">
        <div class="sb_date">
          <p>我的一天</p>
          <span class="date"></span>
        </div>
        <div class="sb_caption">To-Do List</div>
      </div>

      <!-- //输入框 -->
      <form class="info" autocomplete="off">
        <input type="text" class="task" placeholder="+ Add Your Task">
        <button class="add">Add Task</button>
      </form>
      <!-- 错误提示 -->
      <div class="error_message"></div>
      <!-- 任务内容 -->
      <div class="assignment">
        <div class="to_doTask">To-Do</div>
        <ul class="task_list">

        </ul>
        <template class="pattern1">
          <div class="custom-select">
            <span class="placeholder">推迟</span>
            <select name="time" class="hidden-select">
              <option value="0">现在</option>
              <option value="0.1">5分钟后</option>
              <option value="15">15分钟后</option>
              <option value="30">30分钟后</option>
              <option value="60">1小时后</option>
              <option value="180">2小时后</option>
              <option value="1440">明天</option>
              <option value="0">选中</option>
            </select>
          </div>
        </template>
      </div>
    </div>
    <!-- 时间沙漏 -->
    <div class="subject_right">
      <div class="container">
        <div class="timer" id="timer">00:00</div>
        <div class="hourglass">
          <div class="hourglass-shape">
            <div class="top-triangle"></div>
            <div class="bottom-triangle"></div>
          </div>
          <div class="sand top-sand" id="topSand"></div>
          <div class="middle"></div>
          <div class="sand bottom-sand" id="bottomSand"></div>
        </div>
        <div class="controls">
          <button id="pauseBtn" onclick="pauseResumeTimer()" disabled>暂停</button>
          <button onclick="resetTimer()" class="resetButton test111">重置</button>
        </div>
      </div>
    </div>

  </div>

</body>

</html>

2.CSS代码

这里我定义的less文件,它是会自动转化为CSS文件代码的,是一个比较方便的CSS写法,其实就可以写嵌套代码

body {
  margin: 0;
  padding: 0;
  // width: 100vw;
  // height: 100vh;
  overflow-y: scroll;
}

.bg_video {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  object-fit: cover;
}

@keyframes colorChange {
  0% {
    background-position: 0% 50%;
  }

  70% {
    background-position: 100% 50%;
  }

  100% {
    background-position: 0% 50%;
  }
}

.title {
  font-size: 60px;
  margin-top: 50px;
  text-align: center;
  background-image: linear-gradient(to right, #ff0000, #b9e3f0, #0000ff);
  background-size: 200% 200%;
  animation: colorChange 8s ease infinite;
  //背景图像限制在文本区域内
  background-clip: text;
  //文字设为透明
  -webkit-text-fill-color: transparent;
}

.subject {
  position: relative;
  display: flex;
  width: 980px;
  height: 490px;
  margin: 50px auto;
  border-radius: 5%;
  border: 2px #d9b949 solid;
  overflow: hidden;

  .subject_left {
    width: 600px;
    background: url(./images/mission-candlestick-7617552_1280.webp) no-repeat;
    background-size: cover;
    opacity: .8;

    .sb_title {
      display: flex;
      margin-top: 0px;
      height: 80px;

      .sb_date {
        margin-top: 35px;
        width: 200px;
        //background-color: blue;

        p {
          text-align: center;
          font-size: 20px;
          color: #07aa93;
        }

        .date {
          margin-top: 5px;
          display: block;
          text-align: center;
          font-size: 15px;
          color: #adb1b5;
        }
      }

      .sb_caption {
        width: 200px;
        margin: 30px 25px 0;
        color: #8fb4d1;
        font-size: 25px;
        text-align: center;
        border-bottom: 1px solid #0681be;
      }
    }

    .info {
      margin-top: 30px;

      .task {
        width: 400px;
        height: 40px;
        background-color: transparent;
        border: 1px solid transparent;
        margin-left: 30px;
        color: #999;
        border-bottom: 2px solid #0681be;

      }

      .add {
        width: 80px;
        height: 30px;
        margin-left: 20px;
        background-color: #a96f34;
        border-radius: 15px;
      }
    }

    .assignment {
      width: 430px;
      height: 290px;
      margin-top: 10px;
      margin-left: 30px;
      border: 2px solid #90b4d1;
      border-radius: 10%;

      overflow: auto;
      scrollbar-width: none;
      /* Firefox */
      -ms-overflow-style: none;
      /* Internet Explorer 和 Edge */


      .to_doTask {
        width: 80px;
        height: 30px;
        margin-top: 10px;
        margin-left: 10px;
        border-bottom: 2px solid #f2b11c;
        text-align: center;
        color: #f2b11c;

      }

      //任务栏
      .task_list {
        overflow: auto;
      }

      //时间栏
      .timeDelay {
        width: 80px;
        height: 200px;
      }

      /* 选择框容器样式 */
      .custom-select {
        position: relative;
        display: inline-block;
        width: 60px;
        height: 23px;
        line-height: 23px;
      }

      /* 占位元素样式 */
      .custom-select .placeholder {
        display: block;
        text-align: center;
        //font-size: inherit;
        color: #895d31;
        background-color: #cccdcf;
        border-radius: 4px;
        cursor: pointer;
      }

      /* 隐藏的选择框样式 */
      .custom-select .hidden-select {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        opacity: 0;
        cursor: pointer;
      }

      /* 选择时间显示区域 */
      .selected-time {
        margin-top: 10px;
        font-weight: bold;
      }
    }

    .assignment::-webkit-scrollbar {
      display: none;
    }

    .error_message {
      color: red;
      height: 12px;
      margin-left: 30px;
      margin-top: 5px;
      font-size: 12px;
      font-weight: 700;
    }

  }

  .subject_right {
    width: 380px;
    background: url(./images/385dd3ba7d76551.jpg) no-repeat;
    background-size: cover;
    opacity: .8;
    border-left: 2px solid black;

    // body {
    //   font-family: Arial, sans-serif;
    //   display: flex;
    //   justify-content: center;
    //   align-items: center;
    //   height: 100vh;
    //   margin: 0;
    //   background-color: #f0f0f0;
    // }

    .container {
      text-align: center;
    }

    .hourglass {
      width: 200px;
      height: 300px;
      position: relative;
      margin: 30px auto;
    }

    .hourglass-shape {
      width: 100%;
      height: 100%;
      position: absolute;
      border: 4px solid #b3b7bb;
    }

    .top-triangle,
    .bottom-triangle {
      width: 0;
      height: 0;
      position: absolute;
      left: 50%;
      transform: translateX(-50%);
    }

    .top-triangle {
      top: 0;
      border-left: 100px solid transparent;
      border-right: 100px solid transparent;
      border-top: 150px solid #b3b7bb;
    }

    .bottom-triangle {
      bottom: 0;
      border-left: 100px solid transparent;
      border-right: 100px solid transparent;
      border-bottom: 150px solid #b3b7bb;
    }

    .sand {
      position: absolute;
      left: 50%;
      transform: translateX(-50%);
      background-color: #f1c40f;
      transition: height 0.1s linear;
    }

    .top-sand {
      top: 4px;
      clip-path: polygon(0% 0%, 100% 0%, 50% 100%);

    }

    .bottom-sand {

      bottom: 4px;
      clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
    }

    .middle {
      width: 20px;
      height: 20px;
      background-color: #b3b7bb;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%) rotate(45deg);
    }

    .timer {
      font-size: 24px;
      margin-top: 15px;
      color: #f8fbf1;
    }

    .controls button {
      padding: 10px 20px;
      font-size: 16px;
      margin: 0 5px;
      cursor: pointer;
      background-color: #3498db;
      color: white;
      border: none;
      border-radius: 5px;
      transition: background-color 0.3s;
    }

    .controls button:hover {
      background-color: #2980b9;
    }

    .controls button:disabled {
      background-color: #95a5a6;
      cursor: not-allowed;
    }
  }
}

/* 创建的组件属性 */
.taskList {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 380px;
  height: 50px;
  border: 1px solid #f2b11c;
  border-radius: 20px;
  margin: 10px 0 0 20px;

}

//显示文本
.task_vlaue {
  font-size: 18px;
  width: 200px;
  color: #c7cfd5;
  margin: 10px 20px;
  margin-right: 15px;
  cursor: text;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

//显示时间
.task_time {
  //text-align: right;
  flex-grow: 1;
  font-size: 12px;
  color: #0681be;
}


.checkbox {
  //appearance: none;
  width: 20px;
  height: 20px;
  border: 1px solid #ccc;
  border-radius: 3px;
  outline: none;
  cursor: pointer;
  margin-left: 10px;
  //background-image: url(./images/玩球的猫.png);
  // background-size: contain;
  // background-repeat: no-repeat;
}

//功能按钮
.taskFunction {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  width: 60px;
  height: 48px;
  background-color: #07203f;
  margin-right: 20px;
  cursor: pointer;
}

//推迟按钮
.taskRemind {
  text-align: center;
  width: 60px;
  height: 23px;
  line-height: 23px;
  color: #895d31;
  background-color: #cccdcf;
  border-radius: 5px;
}

//关闭按钮

.taskList .taskDelete {
  text-align: center;
  width: 60px;
  height: 23px;
  line-height: 23px;
  color: #895d31;
  background-color: #cccdcf;
  border-radius: 5px;
  text-decoration: none !important;
}

.hintColor {
  background-color: #fff;
}

//点击复选框出现复选框
.completed {
  text-decoration: line-through;
}

//点击复选框禁用其他按钮
.aaaa {
  pointer-events: none;
}

.bbbb {
  pointer-events: none;
}

3.JavaScript代码

 


//设置随机颜色标题
function getRandomColor() {
  const letters = '0123456789ABCDEF'
  let color = '#'
  for (let i = 0; i < 6; i++) {
    color += letters[Math.floor(Math.random() * 16)];
  }
  return color
}

function updateRandomColor() {
  const title = document.querySelector('.title')
  const color1 = getRandomColor();
  const color2 = getRandomColor();
  const color3 = getRandomColor();
  title.style.color = getRandomColor()
  title.style.backgroundImage = `linear-gradient(to right, ${color1}, ${color2}, ${color3})`
}

updateRandomColor()
setInterval(updateRandomColor, 1000)

//表单左侧内容---------------------------------------------------------------------------

//表单日期
function getdate() {
  const currentDate = new Date()
  //const year = currentDate.getFullYear()
  const month = String(currentDate.getMonth() + 1).padStart(2, '0')
  const day = String(currentDate.getDate()).padStart(2, '0')
  const hours = String(currentDate.getHours()).padStart(2, '0')
  const minutes = String(currentDate.getMinutes()).padStart(2, '0')
  //const seconds = String(currentDate.getSeconds()).padStart(2, '0')
  return `${month}/${day} ${hours}:${minutes}`
}
setInterval(function () {
  document.querySelector('.date').innerHTML = new Date().toLocaleString()
}, 1000)

//任务添加----------------------------------------------------

//添加数据到本地
console.log(new Date().toLocaleString())
const info = document.querySelector('.info')
const task = document.querySelector('.task')
const error_message = document.querySelector('.error_message')
const data = JSON.parse(localStorage.getItem('data')) || []

//数据提交
const taskList = document.querySelector('.task_list')

renderTask()

info.addEventListener('submit', function (e) {
  e.preventDefault()
  //去除前后空格
  let taskValue = task.value.trim()
  if (taskValue) {
    error_message.textContent = ''
    data.push(
      {
        value: taskValue,
        time: getdate(),
        actionTime: '0',
        status: 'fasle'
      }
    )
    while (taskList.firstChild) {
      taskList.removeChild(taskList.firstChild)
    }
    renderTask()
  } else {
    error_message.textContent = '请输入任务内容'
  }
  saveTasksStorage()
  this.reset()
})


//检测输入框上是否有数据
task.addEventListener('input', () => {
  if (error_message.textContent !== '') {
    error_message.textContent = ''
  }
})
//保存数据到本地
function saveTasksStorage() {
  localStorage.setItem('data', JSON.stringify(data))
}

//渲染数据
function renderTask() {
  while (taskList.firstChild) {
    taskList.removeChild(taskList.firstChild)
  }
  for (let i = 0; i < data.length; i++) {
    const task = data[i]
    const taskLine = addTask(task)
    const checked = taskLine.querySelector('.checkbox')
    if (task.status === 'true') {
      taskLine.classList.add('completed')
      taskLine.classList.add('aaaa')
      checked.checked = 'true'
      checked.style.pointerEvents = 'auto'
      taskList.appendChild(taskLine)
    } else {
      taskList.appendChild(taskLine)
    }
  }
}

//添加数据
function addTask(allTask) {
  const taskLine = document.createElement('li')
  taskLine.classList.add('taskList')
  const taskRadio = document.createElement('input')
  taskRadio.type = 'checkbox';
  taskRadio.classList.add('checkbox')

  //复选框操作 
  taskRadio.addEventListener('change', () => {
    const taskIndex = Array.from(taskLine.parentNode.children).indexOf(taskLine);
    if (taskRadio.checked) {
      taskLine.classList.add('completed')
      taskLine.classList.add('aaaa')
      taskRadio.style.pointerEvents = 'auto'
      data[taskIndex].status = 'true'
      checkPause()
      resetTimer()
    } else {
      taskLine.classList.remove('completed')
      // 取消之前设置的定时器
      taskLine.classList.remove('aaaa')
      data[taskIndex].status = 'false'
      checkStart()
      //clearTimeout(taskLine.dataset.timeoutId)
    }
    saveTasksStorage()
  })

  //文本显示和编辑
  const taskValue = document.createElement('span')
  taskValue.textContent = allTask.value
  taskValue.classList.add('task_vlaue')
  taskValue.setAttribute('contenteditable', 'true')
  taskValue.setAttribute('spellcheck', 'false')
  taskValue.addEventListener('input', () => {
    taskValue.addEventListener('keydown', (event) => {
      // 检查是否按下了 Enter 键
      if (event.key === 'Enter') {
        // 阻止默认的换行行为
        event.preventDefault()
        // 获取更改后的内容
        const updatedContent = taskValue.textContent.trim()
        const index = Array.from(taskLine.parentNode.children).indexOf(taskLine)
        // 将更改后的内容保存到本地存储
        data[index].value = updatedContent
        if (updatedContent === '') {
          taskValue.textContent = '无标题'
        } else {
          // 将更改后的内容保存到本地存储
          localStorage.setItem('data', JSON.stringify(data))
        }
        // 触发 blur 事件,使编辑状态结束
        taskValue.blur();
      }
    })
  })

  const taskTime = document.createElement('span')
  taskTime.textContent = allTask.time
  taskTime.classList.add('task_time')

  const taskFunction = document.createElement('div')
  taskFunction.classList.add('taskFunction')

  //推迟
  const template1 = document.querySelector('.pattern1')
  const taskRemind = template1.content.cloneNode(true);

  //关闭按钮
  const taskDelete = document.createElement('div')
  taskDelete.classList.add('taskDelete')
  taskDelete.textContent = '关闭'
  taskDelete.addEventListener('click', () => {
    if (data.length <= +localStorage.getItem('indexNow')) {
      localStorage.setItem('indexNow', '0')
      saveTasksStorage()
    }
    deletetask(taskLine);
  });

  taskFunction.appendChild(taskRemind)
  taskFunction.appendChild(taskDelete)
  taskLine.appendChild(taskRadio)
  taskLine.appendChild(taskValue)
  taskLine.appendChild(taskTime)
  taskLine.appendChild(taskFunction)
  selecttime(taskLine)
  return taskLine
}

//删出数据
function deletetask(taskLine) {
  const taskIndex = Array.from(taskList.childNodes).indexOf(taskLine)
  console.log(taskIndex)
  data[taskIndex].actionTime = '0'
  data.splice(taskIndex, 1);
  saveTasksStorage()
  //taskLine.remove();
  while (taskList.firstChild) {
    taskList.removeChild(taskList.firstChild);
  }
  resetTimer()
  renderTask();
}



//获取你选择的执行时间
function selecttime(taskLine) {
  //const func = taskLine.querySelector('.taskFunction')
  // const thirdChild = taskLine.children[2];
  taskLine.children[3].addEventListener('change', function (event) {
    // taskLine.style.backgroundColor = ''
    const delaySelectValue = +(event.target.value * 60)
    const delayIndex = Array.from(taskList.childNodes).indexOf(taskLine)
    console.log(delayIndex);
    data[delayIndex].actionTime = +data[delayIndex].actionTime + delaySelectValue
    // let indexNow = delayIndex
    //当前你的时间记录行的索引
    localStorage.setItem('indexNow', JSON.stringify(delayIndex))
    //更新数据到本地
    saveTasksStorage()
    //执行你所选的倒计时时间
    startTimer(data[delayIndex].actionTime, taskLine)

    const childrenLine = document.querySelectorAll('.taskList')
    childrenLine.forEach(child => {
      childrenLine.forEach(c => c.classList.remove('hintColor'));
      taskLine.classList.add('hintColor')
    });
    // 为当前点击的子元素添加 'active' 类
    //taskLine.classList.add('hintColor');
    //taskLine.classList.add('hintColor')
  })
}

//刷新页面时显示倒计时正在进行哪一行
(function () {
  const childrenLine = document.querySelectorAll('.taskList')
  if (childrenLine.length > 0) {
    childrenLine[+localStorage.getItem('indexNow')].classList.add('hintColor')
  }
})()

//时间沙漏

let timer
let totalSeconds = 0
let elapsedSeconds = 0
let isRunning = false

const topSand = document.getElementById('topSand')
const bottomSand = document.getElementById('bottomSand')
const timerDisplay = document.getElementById('timer')
const pauseBtn = document.getElementById('pauseBtn')

//当页面刷新时继续倒计时上次的
if (data.length > 0) {
  startTimer2(data[+localStorage.getItem('indexNow')].actionTime, data[+localStorage.getItem('indexNow')])
}

function startTimer2(minutes, taskLine) {
  resetTimer()
  if (minutes === null) {
    minutes = 0
  }
  totalSeconds = minutes
  elapsedSeconds = 0
  if (data[+localStorage.getItem('indexNow')].status === 'false') {
    isRunning = true
    pauseBtn.disabled = false
    updateTimer()

    timer = setInterval(() => {
      if (isRunning) {
        updateTimer()
        elapsedSeconds++
        if (elapsedSeconds > totalSeconds) {
          pauseBtn.disabled = true
          //时间到去除这项任务
          setTimeout(function () {
            //deletetask(taskLine)
          }, 2000)
          clearInterval(timer)
        }
      }
    }, 1000)

  }
}


function startTimer(minutes, taskLine) {
  resetTimer()
  if (minutes === null) {
    minutes = 0
  }
  totalSeconds = minutes
  elapsedSeconds = 0
  isRunning = true
  pauseBtn.disabled = false
  updateTimer()
  timer = setInterval(() => {
    if (isRunning) {
      updateTimer()
      elapsedSeconds++
      if (elapsedSeconds > totalSeconds) {
        pauseBtn.disabled = true
        //时间到去除这项任务
        setTimeout(function () {
          //deletetask(taskLine)
        }, 2000)
        clearInterval(timer)
      }
    }
  }, 1000)
}
//更新时间
function updateTimer() {
  const progress = elapsedSeconds / totalSeconds
  topSand.style.width = `${200 * (1 - progress)}px`
  topSand.style.height = `${150 * (1 - progress)}px`
  bottomSand.style.width = `${200 * progress}px`
  bottomSand.style.height = `${150 * progress}px`

  const remainingSeconds = totalSeconds - elapsedSeconds
  const minutes = Math.floor(remainingSeconds / 60)
  const seconds = remainingSeconds % 60
  timerDisplay.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`

  //跟新本地数据时间
  data[+localStorage.getItem('indexNow')].actionTime = remainingSeconds
  saveTasksStorage()
}

//时间的暂停
function pauseResumeTimer() {
  isRunning = !isRunning;
  pauseBtn.textContent = isRunning ? '暂停' : '继续'
}

//当结束任务是的时间状态

function checkPause() {
  isRunning = false;
  pauseBtn.disabled = true
}

function checkStart() {
  isRunning = false
  pauseBtn.disabled = false
  pauseBtn.textContent = isRunning ? '暂停' : '继续'
}

//重置沙漏状态
function resetTimer() {
  clearInterval(timer)
  elapsedSeconds = 0
  isRunning = false
  topSand.style.width = '200px'
  topSand.style.height = '150px'
  bottomSand.style.width = '0px'
  bottomSand.style.height = '0px'
  timerDisplay.textContent = '00:00'
  pauseBtn.textContent = '暂停'
  pauseBtn.disabled = true
}

//重置并清空时间
const resetButton = document.querySelector('.test111')
resetButton.addEventListener('click', function () {
  clearInterval(timer)
  data[localStorage.getItem('indexNow')].actionTime = '0'
  localStorage.setItem('data', JSON.stringify(data))
})

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值