前端炫技合集,简单的TODoList,简单的技术,实现不简单的效果

简介: 前端炫技合集,简单的TODoList,简单的技术,实现不简单的效果

前言


在我写的之前的文章中,大多数都是针对一个技术点进行讲解,因为我是一个没有创意的人,所以很少写什么有很漂亮的效果的文章或者demo;


而这次是因为之前有一次逛掘金,看到了竞赛的模块,然后手一抖就点了报名,手都抖了那就只能硬着头皮上了,动画效果我就算了,本来就没啥创意,那就来一个简单的 ToDoList 吧;


但是我不允许我的 ToDoList 太 low,于是开始大量的参考别人设计的UI,然后加上自己的一些想法和创意,最后完成了这个简单的 ToDoList;


我要我的 ToDoList 不仅好看,还要能看很多技术点,同时这些个技术点不需要用的有多高明,让小白都能上手都能看懂一二,接下来就开始正式的炫技;


先看效果,感觉不错的话麻烦帮我在码上掘金中点个赞,非常感谢!!!

在 PC 上全屏查看效果最佳!

image.png

分解


image.png

我的 ToDoList 一共分为四个大部分,如上图所示,分别是:


  • 顶部的标题栏
  • 添加任务的输入框
  • 显示任务的列表
  • 浮动右侧的筛选


当然还需要一个好看的背景,这个就不算了,我直接一个线性渐变就搞定了;


css 部分


顶部的标题栏

image.png

顶部标题栏可以看到是一个艺术字的效果,有点立体的感觉,同时可以看大字体的颜色和背景色会有混合的效果;


其实这些样式都非常简单,css代码如下:

.todo-title {
  font-family: 'Helvetica Neue', sans-serif;
  font-size: 60px;
  font-weight: bold;
  font-style: italic;
  text-align: center;
  text-transform: uppercase;
  color: #333;
  text-shadow: 1px 1px 0 #999, 2px 2px 0 #888, 3px 3px 0 #777, 4px 4px 0 #666, 5px 5px 0 #555, 6px 6px 0 #444, 7px 7px 6px rgba(0, 0, 0, 0.4);
  mix-blend-mode: color-burn;
}

首先随便设置一下字体样式,然后使用text-transform属性将字母转换为大写;


使用text-shadow属性设置文字的阴影,这里设置了七个阴影,使字体样式变的立体;


最后使用mix-blend-mode属性设置混合模式,这里使用的是color-burn,这个属性的作用是将文字的颜色和背景色进行混合;


可以看到这里并没有什么太多的高大上的技术,不熟悉的属性大概也就一两个,但是最后实现的效果很不错,不管你觉不觉得,反正我是这样觉得的;


友情提示:这次不讲技术点,只讲实现效果的代码,对技术点不熟悉的可以去MDN查看相关属性的用法;

添加任务的输入框

image.png

这里的样式没啥好说的,就是一个输入框加一个按钮,但是可以看到这里的效果,输入框获取焦点的时候,整个包裹输入框的容器会有一个阴影回收聚焦的效果;


这里的效果也是非常简单,css代码如下:

.input-wrap:has(input:focus) {
  box-shadow: 2px 2px 1px 2px rgba(0, 0, 0, 0.5);
}

这么炫酷的效果居然只有一行代码,这里使用了csshas伪类选择器,这个选择器的作用是选择包含指定选择器的元素;


可以理解为如果.input-wrap下面的input元素获取到了焦点,那么就会给.input-wrap添加一个阴影;


其他的css就是一些简单的样式,没有什么特别的属性,这里就不贴出来了;


显示任务的列表

image.png

显示任务的列表是整个 ToDoList 的核心,这里的样式也是最复杂的,但是也是最有意思的;


首先这里有三种状态的任务,分别是进行中已完成未完成


他们的样式看起来是一样的,就是背景色不同,现在只是讲css部分,就先忽略它们之间的区别;

这里的样式也是非常简单,css代码如下:

  .list-item {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-radius: 4px;
    background-image: linear-gradient(90deg, lighten(@active-color, 10%), darken(@active-color, 10%));
    margin-bottom: 10px;
    padding: 10px;
    color: #fff;
    cursor: pointer;
    overflow: hidden;
    transition: all 2s;
    &::after {
      position: absolute;
      content: '';
      top: 0;
      left: 0;
      width: 0;
      height: 100%;
      z-index: -1;
      transition: all 2s;
    }
    &.success::after {
      background-image: linear-gradient(90deg, lighten(@success-color, 10%), darken(@success-color, 10%));
      width: 100%;
    }
    &.fail::after {
      background-image: linear-gradient(90deg, lighten(@fail-color, 10%), darken(@fail-color, 10%));
      width: 100%;
    }
    svg {
      margin-right: 10px;
      path {
        fill: transparent;
        stroke: transparent;
        stroke-width: 3;
        stroke-dasharray: 100;
        stroke-dashoffset: 100;
      }
    }
    label {
      font-size: 16px;
      color: #fff;
      flex-grow: 1;
      cursor: pointer;
    }
  }

这里贴出的是全部的css代码,里面没有写注释,因为不需要全部都看懂,只需要看懂其中的一部分就可以了;


首先进行中的样式的颜色是使用linear-gradient属性设置的,而已完成未完成的样式是使用::after伪元素设置的;


因为已完成未完成最后会有一个动画效果,所以这里使用了::after伪元素来完成这个动画效果;


可以看到里面还有svg的样式,没想到还能掺杂svg的知识点吧,这里的svg是用来实现已完成未完成的勾选图标的,同时里面也有动画效果,后面会讲到;


这里只要的技术点是lesslightendarken函数,以及less的变量;


lighten函数的作用是将颜色变亮,darken函数的作用是将颜色变暗,这里的颜色是使用less的变量来设置的;


同时可以看到这个顶部有一个小提示的标题,这个标题使用了position: sticky属性,这个属性的作用是让元素在滚动到指定位置的时候固定在页面上;


浮动右侧的筛选


这里其实没有什么技术点,就是一个简单的绝对定位,没有动画效果,就不贴效果图和代码了;


js 部分


这里使用的技术是vue3,所以js方面也就是vue3的知识点;


添加任务


首先我这里没有删除任务的功能,也没有修改任务的功能,只有添加任务和修改任务状态的功能;


这一块只需要一个list就可以搞定了,然后新增的时候就直接往里面push,修改状态的时候就直接修改list里面的数据就可以了;


首先在输入框上面绑定一个text,然后在按钮上面绑定一个click事件,这里的click事件就是用来添加任务的;


<template>
  <div class="input-wrap" @keyup.enter="handleAdd">
    <input v-model="text" placeholder="新增一个待办事项吧"/>
    <button @click="handleAdd">新增</button>
  </div>
</template>
<script setup>
import { ref } from "vue";
const text = ref('');
const list = ref([]);
const handleAdd = () => {
  if (text.value === '') return;
  list.value.push({
    id: Math.random().toString(36).substr(2),
    status: 'active',
    label: text.value,
  });
  text.value = '';
}
</script>

可以看到我在最外层的div上面绑定了一个keyup.enter事件,这样我们就可以使用回车的方式来添加任务了;


这里的逻辑很简单,调用handleAdd就会往list里面push一个对象,这个对象里面有idstatuslabel三个属性;


id是一个随机的字符串,作为一个唯一的标识,用于后续的transition动画;


status是任务的状态,这里的状态有三种,分别是activesuccessfail,没有使用TS就没有枚举,也懒得全局添加常量了;


label就是任务的内容,这里的label是从输入框里面获取的,所以这里需要一个text来绑定输入框的内容;


渲染任务


因为会有过滤状态,所以渲染任务并不是直接使用list来渲染的,而是使用computed来渲染的;

<template>
  <div class="todo-list">
    <div class="todo-tip">
      今日事,今日毕,今天还有 {{ remainder }} 件事情要做
    </div>
    <div
        v-for="item in filterList"
        :key="item.id"
        :class="['list-item', item.status]"
    >
      <!--        <svg>成功的svg省略</svg>-->
      <!--        <svg>失败的svg省略</svg>-->
      <label v-text="item.label"/>
    </div>
    <div class="filter-bar">
      <div
          :class="['filter-bar-item', filterStatus === '' && 'active']"
          @click="filterStatus = ''"
      >
        全部
      </div>
      <div
          :class="['filter-bar-item', filterStatus === 'active' && 'active']"
          @click="filterStatus = 'active'"
      >
        待完成
      </div>
      <!--      <div>已完成、未完成省略</div>-->
    </div>
  </div>
</template>
<script setup>
import { ref, computed } from "vue";
// 进行中的数量
const remainder = computed(() => list.value.filter(item => item.status === 'active').length);
// 过滤状态标识
const filterStatus = ref('');
// 过滤后的任务列表
const filterList = computed(() => {
  if (filterStatus.value === '') {
    return list.value;
  }
  return list.value.filter(item => item.status === filterStatus.value);
});
</script>

上面为了方便阅读代码,删除了一些不相关的代码,省略了一些相同代码;


这里就是computed函数的运用了,使用在了两个地方,一个是remainder表示当前进行中的任务数量,还有一个是filterList表示过滤后的任务列表;


然后可以看到右侧的过滤列表的class绑定,这里并没使用三元表达式,而是使用了&&


:class="['filter-bar-item', filterStatus === 'active' && 'active']"


这个表达式的意思是,如果前面一个条件成立,那么就会返回后面的值,如果前面一个条件不成立,那么就会返回false,这样就不会添加active这个class了;


一个小操作,可以比使用三元表达式更加简洁,还有||也是一样的,看各位的实际场景来使用;


修改任务状态


这里修改任务状态就是对列表中的status进行修改,然后filterList就会自动过滤掉;

<template>
  <div class="todo-list">
    <div
        v-for="item in filterList"
        :key="item.id"
        :class="['list-item', item.status]"
    >
      <svg @click="handleDone(item, 'success')">
        省略具体的svg
      </svg>
      <svg @click="handleDone(item, 'fail')">
        省略具体的svg
      </svg>
      <label v-text="item.label"/>
    </div>
  </div>
</template>
<script setup>
const handleDone = (item, status) => {
  item.status = status;
}
</script>

可以看到我这里用了两个svg来表示任务的完成状态按钮,也就是大家看到最前面截图的列表上前面两个框框;


这里的代码很简单,就是调用handleDone函数,然后传入当前的itemstatus,然后修改itemstatus就可以了;


剩下的交给computedvue的响应式就可以了;


js想关的到这里就差不多了,剩下的就是动画部分了;


动画


动画最开始一个就是输入框的聚焦效果,没啥好说的,就是一个简单的transition属性就搞定了;


抛开这个,今天要介绍的动画效果有四个,分别是:


  • vuetransition动画
  • svg描边动画
  • csstransition动画
  • cssanimation动画


vuetransition动画


这个动画是vue内置的组件,在我这里的应用就是任务列表的过渡动画;

image.png

<template>
  <div class="todo-list">
    <transition-group
      name="fade"
      mode="out-in"
      :duration="500"
      tag="div"
      style="height: 500px; overflow: auto; margin: 0 -20px; padding: 0 20px;"
    >
      <div
        v-for="item in filterList"
        :key="item.id"
        :class="['list-item', item.status]"
      >
       <!--省略-->
      </div>
    </transition-group>
  </div>
</template>
<style lang="less">
.fade-enter-active,
.fade-leave-active {
  transition: all 0.3s ease-in-out;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
  transform: translateY(-20px);
}
</style>

这是一个非常简单的渐入的过渡动画效果,可以看下面的截图,每次添加任务的时候会渐入的添加进来:


除了上面这个效果以外,还有就是列表切换的时候,也会有一个渐出的效果,这里大家可以自己在码上掘金中体验一下;


svg描边动画

image.png


svg的描边动画在这个里面是用来表示任务的完成状态的,也就是前面提到的两个框框;

<template>
  <div class="todo-list">
    <div
        v-for="item in filterList"
        :key="item.id"
        :class="['list-item', item.status]"
    >
      <svg
          :class="['success-icon', item.status]"
          xmlns="http://www.w3.org/2000/svg"
          width="32"
          height="32"
          viewBox="0 0 32 32"
          @mouseenter="handleHover($event, item, true)"
          @mouseleave="handleHover($event, item, false)"
      >
        <rect x="1" y="1" rx="4" ry="4" width="30" height="30" stroke="#fff" fill="transparent" stroke-width="2"/>
        <path d="M6 14l6 8 L24 10">
          <animate
              attributeName="stroke-dashoffset"
              from="100"
              to="0"
              dur="1s"
              begin="0s"
              fill="freeze"
          />
        </path>
      </svg>
      <svg
          :class="['fail-icon', item.status]"
          xmlns="http://www.w3.org/2000/svg"
          width="32"
          height="32"
          viewBox="0 0 32 32"
          @mouseenter="handleHover($event, item, true)"
          @mouseleave="handleHover($event, item, false)"
      >
        <rect x="1" y="1" rx="4" ry="4" width="30" height="30" stroke="#fff" fill="transparent" stroke-width="2"/>
        <path d="M8,8 L23,23 M23,8 L8,23" transform="rotate(-90, 15.5, 15.5)">
          <animate
              attributeName="stroke-dashoffset"
              from="100"
              to="0"
              dur="1s"
              begin="0s"
              fill="freeze"
          />
        </path>
      </svg>
      <label v-text="item.label"/>
    </div>
  </div>
</template>
<script setup>
const handleHover = (e, item, hasHover) => {
  if (item.hover === hasHover) return;
  item.hover = hasHover;
  const path = e.target.getElementsByTagName('path')[0];
  // 播放动画
  if (hasHover) {
    path.style.stroke = '#fff'
    e.target.getElementsByTagName('animate')[0].beginElement();
  } else {
    path.style.stroke = 'transparent';
  }
}
</script>

这里首先简简单单的画两个svg,里面的rect就是我们看到的框框,然后path就是我们看到的对勾和叉叉;


path里面再加上一个animate标签,用来控制描边动画的效果;


但是这里有一个问题,就是svganimate在一放到页面就会播放动画,这里就必须要用到js来控制了;

这里通过mouseentermouseleave来控制hover的状态,然后通过js来控制animate的播放;


为了在不是hover的时候,不显示对钩和叉叉,所以在mouseleave的时候,把pathstroke设置为transparent


mouseenter的时候,把pathstroke设置为#fff,这样就可以看到对钩和叉叉了;


然后执行beginElement方法,就可以播放动画了;


cssanimation动画和transition动画

image.png

cssanimationtransition放在一起讲,因为都是任务状态切换的动画效果;


cssanimation动画应用在状态的切换,任务完成会播放一个庆祝的动画(放大缩小),任务失败会播放一个失败的动画(抖动);


csstransition动画就是背景色从0100%填充的一个过程;

<template>
  <div class="todo-list">
    <div
        v-for="item in filterList"
        :key="item.id"
        :class="['list-item', item.status]"
    >
      <svg
          :class="['success-icon', item.status]"
          xmlns="http://www.w3.org/2000/svg"
          width="32"
          height="32"
          viewBox="0 0 32 32"
          @click="handleDone(item, 'success')"
      >
        <rect x="1" y="1" rx="4" ry="4" width="30" height="30" stroke="#fff" fill="transparent" stroke-width="2"/>
        <path
            d="M6 14l6 8 L24 10"
        >
          <animate
              attributeName="stroke-dashoffset"
              from="100"
              to="0"
              dur="1s"
              begin="0s"
              fill="freeze"
          />
        </path>
      </svg>
      <svg
          :class="['fail-icon', item.status]"
          xmlns="http://www.w3.org/2000/svg"
          width="32"
          height="32"
          viewBox="0 0 32 32"
          @click="handleDone(item, 'fail')"
      >
        <rect x="1" y="1" rx="4" ry="4" width="30" height="30" stroke="#fff" fill="transparent" stroke-width="2"/>
        <path
            d="M8,8 L23,23 M23,8 L8,23"
            transform="rotate(-90, 15.5, 15.5)"
        >
          <animate
              attributeName="stroke-dashoffset"
              from="100"
              to="0"
              dur="1s"
              begin="0s"
              fill="freeze"
          />
        </path>
      </svg>
      <label v-text="item.label"/>
    </div>
  </div>
</template>
<style lang="less">
/*失败动画,执行一次*/
.list-item.fail {
  animation: shake 0.82s cubic-bezier(.36, .07, .19, .97) both;
  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
  perspective: 1000px;
}
@keyframes shake {
  10%, 90% {
    transform: translate3d(-1px, 0, 0);
  }
  20%, 80% {
    transform: translate3d(2px, 0, 0);
  }
  30%, 50%, 70% {
    transform: translate3d(-4px, 0, 0);
  }
  40%, 60% {
    transform: translate3d(4px, 0, 0);
  }
}
/*庆祝动画,执行一次*/
.list-item.success {
  animation: celebrate 0.82s cubic-bezier(.36, .07, .19, .97) both;
  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
  perspective: 1000px;
}
@keyframes celebrate {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.05);
  }
  100% {
    transform: scale(1);
  }
}
</style>

上面的代码就是cssanimation动画,因为我们使用vue动态添加的class,所以每次状态切换都会播放动画;


animation动画默认只会播放一次,所以无需我们添加额外的属性来做控制;


animation动画的关键点就是keyframes,这里就不多说了;

.todo-list {
  .list-item {
    &::after {
      position: absolute;
      content: '';
      top: 0;
      left: 0;
      width: 0;
      height: 100%;
      z-index: -1;
      transition: all 2s;
    }
    &.success::after {
      background-image: linear-gradient(90deg, lighten(@success-color, 10%), darken(@success-color, 10%));
      width: 100%;
    }
    &.fail::after {
      background-image: linear-gradient(90deg, lighten(@fail-color, 10%), darken(@fail-color, 10%));
      width: 100%;
    }
  }
}

上面省略了额外的一些样式,只保留状态切换背景色填充的样式;


上面的代码就是csstransition动画,原理就是使用::after伪元素,然后通过transition属性来控制背景色的填充;


最开始width0,然后当添加了success或者failclass时,width就会变为100%,然后就会有一个过渡的效果;


这样看起来的效果就是状态切换的时候,背景色会从0100%填充,但是再次状态切换就没有这个效果了,因为::after这个时候的width已经是100%了,所以就没有过渡的效果了;


总结


到这里我这个 ToDoList 相关的技术点已经介绍了七七八八了,不用什么高大上的技术,但是也是一个不错的练手项目;


其中涉及到的技术点有:


  • vue的基本使用
  • vuecomputed计算属性
  • vuetransition过渡动画
  • cssanimation动画
  • csstransition动画
  • csshas属性
  • css的一些布局和其他基础知识
  • less的使用
  • less的颜色函数
  • less变量
  • svganimate动画


这些技术单独拿出来说可能都不是什么难点,但是这些技术点的组合使用,就能够做出一个不错的效果;


并且这里并不是特别深入的使用,非常适合新手练手;


最后这个在参加码上掘金编程挑战大赛,如果可以的话,麻烦在码上掘金中给我点个赞,谢谢;


目录
相关文章
|
6天前
|
缓存 监控 前端开发
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
【4月更文挑战第30天】本文探讨了Flutter应用启动优化策略,包括理解启动过程、资源加载优化、减少初始化工作、界面布局简化、异步初始化、预加载关键数据、性能监控分析以及案例和未来优化方向。通过这些方法,可以缩短启动时间,提升用户体验。使用Flutter DevTools等工具可助于识别和解决性能瓶颈,实现持续优化。
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
|
4天前
|
资源调度 监控 前端开发
第七章(原理篇) 微前端技术之依赖管理与版本控制
第七章(原理篇) 微前端技术之依赖管理与版本控制
|
4天前
|
前端开发 JavaScript UED
第五章(原理篇) 微前端技术之模块联邦与动态加载
第五章(原理篇) 微前端技术之模块联邦与动态加载
|
1天前
|
前端开发 Java Go
从前端到后端:构建现代化Web应用的技术演进
本文探讨了从前端到后端的技术演进,介绍了前端、后端以及多种编程语言,如Java、Python、C、PHP和Go,以及数据库在构建现代化Web应用中的应用。通过深入剖析各个技术领域的发展和应用,读者将对构建高效、可扩展、安全的Web应用有更深入的理解。
|
1天前
|
存储 JavaScript 前端开发
使用Vue.js构建交互式前端界面的技术探索
【5月更文挑战第20天】Vue.js是一款渐进式JavaScript框架,擅长构建交互式前端界面。其核心特性包括响应式数据绑定、组件化开发、指令系统和虚拟DOM,简化开发并提升性能。通过Vue CLI创建项目,拆分组件,结合数据绑定和事件处理实现交互,使用Vue Router管理路由,Vuex进行状态管理,能高效构建现代Web应用。
|
4天前
|
前端开发 Java Go
从前端到后端:构建现代化Web应用的技术实践
本文将介绍如何通过前端和后端技术相结合,构建现代化Web应用的技术实践。我们将探讨前端开发、后端架构以及多种编程语言(如Java、Python、C、PHP、Go)在构建高效、可扩展的Web应用中的应用。
|
4天前
|
前端开发 JavaScript 虚拟化
第四章(原理篇) 前端容器技术
第四章(原理篇) 前端容器技术
|
4天前
|
Web App开发 前端开发 JavaScript
构建跨浏览器兼容的前端应用:技术实践与挑战
【5月更文挑战第16天】构建跨浏览器兼容的前端应用是应对浏览器差异和多样性的挑战。使用现代框架(如React、Vue)能自动转换代码,编写可移植的Web标准代码,结合浏览器兼容性测试工具和Polyfill解决旧浏览器支持问题。关注浏览器更新,应对性能、API差异和样式问题,采用渐进增强、条件判断和CSS Reset策略确保应用在各种浏览器上运行良好。
|
6天前
|
机器学习/深度学习 前端开发 Java
Java与前端:揭开技术浪潮背后的真相
Java与前端:揭开技术浪潮背后的真相
12 1
|
6天前
|
存储 JavaScript 前端开发
使用Vue.js构建交互式前端的技术探索
【5月更文挑战第12天】Vue.js是渐进式前端框架,以其简洁和强大的特性深受开发者喜爱。它聚焦视图层,采用MVVM模式实现数据与视图的双向绑定,简化开发。核心特性包括响应式数据绑定、组件化、模板系统和虚拟DOM。通过创建Vue实例、编写模板及定义方法,可以构建交互式前端,如计数器应用。Vue.js让复杂、交互式的前端开发变得更加高效和易维护。