React 中实现一个 todo 打勾效果

咕咕咕~鸽子精回来了,自从新入职了一家实习之后人都要累鼠了,基本没什么功夫去更新文章;还有一个原因是没什么选题。

正好呢,最近做的一些东西让我燃起了一些些记录的热情,不能再咕咕了。

背景是我自己做的 landing 项目里有一个实现类似飞书里的 todo 的效果,然后我研究了下飞书,写出来了一个 demo。

我们先来提出几个问题,解决了之后,我们也就实现了一个todo

  1. 前面的 icon 用什么来实现。
  2. 如何实现文字不可点击,前方icon可点击的效果?
  3. 如果有多个元素循环渲染的情况下,我们怎么在点击的时候对单个元素做样式的修改?(React)

这里有几个部分需要来讨论:

首先是整个用什么来实现这个前面的 icon,是使用伪元素,还是使用一个真实的元素?

这里我看飞书直接用的伪元素来实现的,感觉可能是能用 css 解决的问题就没有必要再去用 html 去写了,那么大致实现逻辑如下。

html 复制代码
 <div class="todo" id="todo">11111</div>

我们使用伪元素实现这个样式的时候,左侧并不会撑开,也就是说,我们需要去给这个 div 一个靠左的 margin 值。然后再去做具体的打勾框。

css 复制代码
    .todo {
        pointer-events: none;
        margin-left: 20px;
    }

    .todo:before {
        content: " ";
        display: inline-block;
        line-height: normal;
        font-size: 16px;
        border: 1px solid black;
        border-radius: 4px;
        background-position: 50%;
        white-space: normal;
        width: 16px;
        height: 16px;
        margin-right: 8px;
        margin-left: -24px;
        box-sizing: border-box !important;
        position: relative;
        cursor: pointer;
        pointer-events: auto;
        top: 2px;
    }

那么上面这些代码,就可以得到下图的展示效果:

是不是看起来已经有了完整的样子啦。

这是我们的 todo 状态,我们还需要实现一个 done的状态,所以还需要额外的 css 去书写,这里需要注意的是,为了交互更加好看,我们在 hover 的时候会让伪元素的样式变蓝。于是增加了如下代码,有了下图所示的效果

css 复制代码
    .todo:hover:before {
        border: 1px solid rgb(100, 149, 237);
    }

第二个点就是,我们如何实现一个文字不可点击,前方icon可点击的效果呢?

其实在上面的代码中已经展示出来了,为这个 div设置pointer-events: none;,然后给伪元素设置pointer-events: auto;就可以啦。

这些考虑完之后,我再给你们展示一下点击状态的css咋写的:

css 复制代码
    .done {
        text-decoration: line-through;
    }

    .done::before {
        background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEwLjU4OSAzLjkwM2wuODA4LjgwOGEuMzUuMzUgMCAwMTAgLjQ5NUw2LjE4IDEwLjQyNWEuMzUuMzUgMCAwMS0uNDk1IDBMMi43MDMgNy40NDRhLjM1LjM1IDAgMDEwLS40OTVsLjgwOC0uODA4YS4zNS4zNSAwIDAxLjQ5NSAwbDEuOTI1IDEuOTI0IDQuMTYzLTQuMTYzYS4zNS4zNSAwIDAxLjQ5NSAweiIgZmlsbD0iIzMzNzBGRiIvPjwvc3ZnPg==);
    }

那么你给这个 div 再加上 done 的类名,就可以得到下图的效果了:

最后呢,如果我们有多个 div,我们如何去做类名的处理呢?

答案是监听点击事件,然后在点击的时候更换类名,代码如下:

js 复制代码
    const div = document.getElementById('todo');
    div.addEventListener('click', (target) => {
        const classArr = target.currentTarget.className.split(' ');
        if (classArr.length > 1) {
            target.currentTarget.className = 'todo'
        } else {
            target.currentTarget.className = 'todo done'
        }
    })

完整代码如下:

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>
</head>

<body>
    <div class="todo" id="todo">11111</div>
</body>
<script>
    const div = document.getElementById('todo');
    div.addEventListener('click', (target) => {
        const classArr = target.currentTarget.className.split(' ');
        if (classArr.length > 1) {
            target.currentTarget.className = 'todo'
        } else {
            target.currentTarget.className = 'todo done'
        }
    })
</script>
<style>
    .todo {
        margin-left: 20px;
    }

    .todo:before {
        content: " ";
        display: inline-block;
        line-height: normal;
        font-size: 16px;
        border: 1px solid black;
        border-radius: 4px;
        background-position: 50%;
        white-space: normal;
        width: 16px;
        height: 16px;
        margin-right: 8px;
        margin-left: -24px;
        box-sizing: border-box !important;
        position: relative;
        cursor: pointer;
        pointer-events: auto;
        top: 2px;
    }

    .todo:hover:before {
        border: 1px solid rgb(100, 149, 237);
    }

    .done {
        text-decoration: line-through;
    }

    .done::before {
        background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEwLjU4OSAzLjkwM2wuODA4LjgwOGEuMzUuMzUgMCAwMTAgLjQ5NUw2LjE4IDEwLjQyNWEuMzUuMzUgMCAwMS0uNDk1IDBMMi43MDMgNy40NDRhLjM1LjM1IDAgMDEwLS40OTVsLjgwOC0uODA4YS4zNS4zNSAwIDAxLjQ5NSAwbDEuOTI1IDEuOTI0IDQuMTYzLTQuMTYzYS4zNS4zNSAwIDAxLjQ5NSAweiIgZmlsbD0iIzMzNzBGRiIvPjwvc3ZnPg==);
    }
</style>

</html>

好啦,那么这就是本期文章的全部内容。

这里是不更新就在打工的阳树,我们下次再见~

相关推荐
腾讯TNTWeb前端团队4 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
uhakadotcom8 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪8 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪8 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom9 小时前
快速开始使用 n8n
后端·面试·github
uhakadotcom9 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom9 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom9 小时前
React与Next.js:基础知识及应用场景
前端·面试·github