jjzjj

一颗红心,三手准备,分别基于图片(img)/SCSS(样式)/SVG动画实现动态拉轰的点赞按钮特效

刘悦的技术博客 2023-03-28 原文

华丽炫酷的动画特效总能够让人心旷神怡,不能自已。艳羡之余,如果还能够探究其华丽外表下的实现逻辑,那就是百尺竿头,更上一步了。本次我们使用图片、SCSS样式以及SVG图片动画来实现“点赞”按钮的动画特效,并比较不同之处。

图片实现

最简单,也最容易理解的实现方式就是使用图片。曾几何时,几乎所有前端特效都需要借助图片来完成。

实现原理很简单,通过不同的关键帧来“拼接”一段完整的动画影片,每一帧即该动画的每一个瞬间“状态”。

首先声明必要的盒子模型:

<div class="heart"></div>

这里以div为例子,声明伪类对象heart。

随后通过样式对heart伪类进行控制:

.heart {  
  cursor: pointer;  
  height: 50px;  
  width: 50px;  
  background-image:url( 'https://abs.twimg.com/a/1446542199/img/t1/web_heart_animation.png');  
  background-position: left;  
  background-repeat:no-repeat;  
  background-size:2900%;  
}  
  
.heart:hover {  
  background-position:right;  
}  
  
.is_animating {  
  animation: heart-burst .8s steps(28) 1;  
}  
  
@keyframes heart-burst {  
  from {background-position:left;}  
  to { background-position:right;}  
}

这里指定背景图片位置,只显示最左侧的背景元素,鼠标悬停时则只显示最右侧背景元素,然后当鼠标点击div时,触发@keyframes heart-burst动画,从左侧移动至右侧,一共进行28帧的侧移动作:

这样就完成了一段流畅的动画特效:

See the Pen <a href="https://codepen.io/v3ucom/pen/yLjNERX"> Untitled</a> by 刘悦的技术博客 (<a href="https://codepen.io/v3ucom">@v3ucom</a>) on <a href="https://codepen.io">CodePen</a>.

需要注意的是,这里需要借助JavaScript绑定单击事件,所以需要引入zepto.min.js文件

https://cdnjs.cloudflare.com/ajax/libs/zepto/1.2.0/zepto.min.js

通过zepto.min.js库就可以很方便的操作页面节点:

$(".heart").on('click touchstart', function(){  
  $(this).toggleClass('is_animating');  
});  
  
$(".heart").on('animationend', function(){  
  $(this).toggleClass('is_animating');  
});

逻辑是单击事件触发动画,动画执行过程中再次点击则移除动画效果。

此种方式依赖多帧背景图、CSS动画以及JavaScript事件绑定,显然成本有些高,同时多帧图片平铺导致体积过大,也会占用系统带宽,得不偿失。

纯SCSS(样式)实现

使用纯CSS样式也可以完成这样的特效,但CSS3原生语法太过复杂,这里我们使用SCSS语法,SCSS是 CSS3的超集,在保证兼容性的前提下,允许使用变量、 嵌套、 混合、导入等特性, 在编写逻辑复杂的CSS代码时,可以简化逻辑,提高CSS的代码可读性。

首先还是创建基本盒子模型:

<input id="toggle-heart" type="checkbox"/>  
<label for="toggle-heart">❤</label>

这里通过复选框和标签元素来控制点赞按钮的状态。

随后编写SCSS逻辑:

$bubble-d: 4.5rem; // bubble diameter  
$bubble-r: .5*$bubble-d; // bubble-radius  
  
@mixin bubble($ext) {  
  transform: scale(1);  
  border-color: #cc8ef5;  
  border-width: $ext;  
}  
  
body {  
  display: flex;  
  justify-content: center;  
  margin: 0;  
  height: 100vh;  
}  
  
[id='toggle-heart'] {  
  position: absolute;  
  left: -100vw;  
    
  &:checked + label {  
    color: #e2264d;  
    will-change: font-size;  
    animation: heart 1s cubic-bezier(.17, .89, .32, 1.49);  
      
    &:before, &:after {  
      animation: inherit;  
      animation-timing-function: cubic-bezier(.21, .61, .35, 1);  
    }  
      
    &:before {  
      will-change: transform, border-width, border-color;  
      animation-name: bubble;  
    }  
  }  
}  
  
[for='toggle-heart'] {  
  align-self: center;  
  position: relative;  
  color: #aab8c2;  
  font-size: 2em;  
  cursor: pointer;  
    
  &:before, &:after {  
    position: absolute;  
    z-index: -1;  
    top: 50%; left: 50%;  
    border-radius: 50%;  
    content: '';  
  }  
    
  &:before {  
    box-sizing: border-box;  
    margin: -$bubble-r;  
    border: solid $bubble-r #e2264d;  
    width: $bubble-d; height: $bubble-d;  
    transform: scale(0);  
  }  
}  
  
@keyframes heart {  
  0%, 17.5% { font-size: 0; }  
}  
  
@keyframes bubble {  
  15% { @include bubble($bubble-r); }  
  30%, 100% { @include bubble(0); }  
}

这里首先将复选框按钮移出界面,随后定义bubble对象,这里bubble指的是点击后膨胀的紫色(#cc8ef5)泡泡,直径是4.5rem,并且圆角修饰:$bubble-r: .5*$bubble-d

随后通过id选择器toggle-heart状态判断元素状态,触发heart和bubble动画,在一秒内动态的改变heart元素以及bubble元素的位置、大小以及边框透明度,从而完成动态特效。

接着添加烟花特效:

body {  
  display: flex;  
  justify-content: center;  
  margin: 0;  
  height: 100vh;  
  background: linear-gradient(135deg, #121721, #000);  
  font: 1em verdana, sans-serif;  
}  
  
[id='toggle-heart'] {  
  position: absolute;  
  left: -100vw;  
}  
[id='toggle-heart']:checked + label {  
  color: #e2264d;  
  filter: none;  
  will-change: font-size;  
  -webkit-animation: heart 1s cubic-bezier(0.17, 0.89, 0.32, 1.49);  
          animation: heart 1s cubic-bezier(0.17, 0.89, 0.32, 1.49);  
}  
[id='toggle-heart']:checked + label:before, [id='toggle-heart']:checked + label:after {  
  -webkit-animation: inherit;  
          animation: inherit;  
  -webkit-animation-timing-function: ease-out;  
          animation-timing-function: ease-out;  
}  
[id='toggle-heart']:checked + label:before {  
  will-change: transform, border-width, border-color;  
  -webkit-animation-name: bubble;  
          animation-name: bubble;  
}  
[id='toggle-heart']:checked + label:after {  
  will-change: opacity, box-shadow;  
  -webkit-animation-name: sparkles;  
          animation-name: sparkles;  
}  
[id='toggle-heart']:focus + label {  
  text-shadow: 0 0 3px white,  0 1px 1px white, 0 -1px 1px white,  1px 0 1px white, -1px 0 1px white;  
}  
  
[for='toggle-heart'] {  
  align-self: center;  
  position: relative;  
  color: #888;  
  font-size: 2em;  
  filter: grayscale(1);  
  -webkit-user-select: none;  
     -moz-user-select: none;  
      -ms-user-select: none;  
          user-select: none;  
  cursor: pointer;  
}  
[for='toggle-heart']:before, [for='toggle-heart']:after {  
  position: absolute;  
  z-index: -1;  
  top: 50%;  
  left: 50%;  
  border-radius: 50%;  
  content: '';  
}  
[for='toggle-heart']:before {  
  box-sizing: border-box;  
  margin: -2.25rem;  
  border: solid 2.25rem #e2264d;  
  width: 4.5rem;  
  height: 4.5rem;  
  transform: scale(0);  
}  
[for='toggle-heart']:after {  
  margin: -0.1875rem;  
  width: 0.375rem;  
  height: 0.375rem;  
  box-shadow: 0.32476rem -3rem 0 -0.1875rem #ff8080, -0.32476rem -2.625rem 0 -0.1875rem #ffed80, 2.54798rem -1.61656rem 0 -0.1875rem #ffed80, 1.84982rem -1.89057rem 0 -0.1875rem #a4ff80, 2.85252rem 0.98418rem 0 -0.1875rem #a4ff80, 2.63145rem 0.2675rem 0 -0.1875rem #80ffc8, 1.00905rem 2.84381rem 0 -0.1875rem #80ffc8, 1.43154rem 2.22414rem 0 -0.1875rem #80c8ff, -1.59425rem 2.562rem 0 -0.1875rem #80c8ff, -0.84635rem 2.50595rem 0 -0.1875rem #a480ff, -2.99705rem 0.35095rem 0 -0.1875rem #a480ff, -2.48692rem 0.90073rem 0 -0.1875rem #ff80ed, -2.14301rem -2.12438rem 0 -0.1875rem #ff80ed, -2.25479rem -1.38275rem 0 -0.1875rem #ff8080;  
}  
  
@-webkit-keyframes heart {  
  0%, 17.5% {  
    font-size: 0;  
  }  
}  
  
@keyframes heart {  
  0%, 17.5% {  
    font-size: 0;  
  }  
}  
@-webkit-keyframes bubble {  
  15% {  
    transform: scale(1);  
    border-color: #cc8ef5;  
    border-width: 2.25rem;  
  }  
  30%, 100% {  
    transform: scale(1);  
    border-color: #cc8ef5;  
    border-width: 0;  
  }  
}  
@keyframes bubble {  
  15% {  
    transform: scale(1);  
    border-color: #cc8ef5;  
    border-width: 2.25rem;  
  }  
  30%, 100% {  
    transform: scale(1);  
    border-color: #cc8ef5;  
    border-width: 0;  
  }  
}  
@-webkit-keyframes sparkles {  
  0%, 20% {  
    opacity: 0;  
  }  
  25% {  
    opacity: 1;  
    box-shadow: 0.32476rem -2.4375rem 0 0rem #ff8080, -0.32476rem -2.0625rem 0 0rem #ffed80, 2.1082rem -1.26585rem 0 0rem #ffed80, 1.41004rem -1.53985rem 0 0rem #a4ff80, 2.30412rem 0.85901rem 0 0rem #a4ff80, 2.08305rem 0.14233rem 0 0rem #80ffc8, 0.76499rem 2.33702rem 0 0rem #80ffc8, 1.18748rem 1.71734rem 0 0rem #80c8ff, -1.35019rem 2.0552rem 0 0rem #80c8ff, -0.60229rem 1.99916rem 0 0rem #a480ff, -2.44865rem 0.22578rem 0 0rem #a480ff, -1.93852rem 0.77557rem 0 0rem #ff80ed, -1.70323rem -1.77366rem 0 0rem #ff80ed, -1.81501rem -1.03204rem 0 0rem #ff8080;  
  }  
}  
@keyframes sparkles {  
  0%, 20% {  
    opacity: 0;  
  }  
  25% {  
    opacity: 1;  
    box-shadow: 0.32476rem -2.4375rem 0 0rem #ff8080, -0.32476rem -2.0625rem 0 0rem #ffed80, 2.1082rem -1.26585rem 0 0rem #ffed80, 1.41004rem -1.53985rem 0 0rem #a4ff80, 2.30412rem 0.85901rem 0 0rem #a4ff80, 2.08305rem 0.14233rem 0 0rem #80ffc8, 0.76499rem 2.33702rem 0 0rem #80ffc8, 1.18748rem 1.71734rem 0 0rem #80c8ff, -1.35019rem 2.0552rem 0 0rem #80c8ff, -0.60229rem 1.99916rem 0 0rem #a480ff, -2.44865rem 0.22578rem 0 0rem #a480ff, -1.93852rem 0.77557rem 0 0rem #ff80ed, -1.70323rem -1.77366rem 0 0rem #ff80ed, -1.81501rem -1.03204rem 0 0rem #ff8080;  
  }  
}

新增sparkles对象,通过动态的控制元素的彩色阴影来表现点击后的烟花动态效果:

See the Pen <a href="https://codepen.io/v3ucom/pen/eYrNLWY"> Untitled</a> by 刘悦的技术博客 (<a href="https://codepen.io/v3ucom">@v3ucom</a>) on <a href="https://codepen.io">CodePen</a>.

这里为了增加效果对比度,将背景设置为深色,同时为点赞按钮增加亮色边框:

[id='toggle-heart']:focus + label {  
  text-shadow:   
    0 0 3px #fff,   
    0 1px 1px #fff, 0 -1px 1px #fff,   
    1px 0 1px #fff, -1px 0 1px #fff;  
}

大体上,利用transform属性控制元素的绝对定位、颜色、边框以及盒子模型阴影来完成此种特效,带宽资源占用层面,明显比图片更具优势。

SVG实现

SVG是矢量图形,不受像素的影响,从而使得它在不同的平台或者媒体下表现出的兼容性更好,与此同时,SVG对动画的支持较好,其DOM结构可以被其特定语法或者CSS控制,从而轻松的实现动画效果。

首先依然声明盒子模型:

<label class="like">  
  <input type="checkbox"/>  
  <div class="hearth"/>  
</label>

随后定义样式:

:root {  
  --size: 100px;  
  --frames: 62;  
}  
  
html {  
  background-color: #15202B;  
  min-height: 100vh;  
  display: flex;  
  justify-content: center;  
  align-items: center;  
  user-select: none;  
}  
  
input {  
  display: none;  
}  
  
.like {  
  display: block;  
  width: var(--size);  
  height: var(--size);  
  cursor: pointer;  
  border-radius: 999px;  
  overflow: visible;  
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);  
  -webkit-tap-highlight-color: transparent;  
}  
  
.hearth {  
  background-image: url('https://assets.codepen.io/23500/Hashflag-AppleEvent.svg');  
  background-size: calc(var(--size) * var(--frames)) var(--size);  
  background-repeat: no-repeat;  
  background-position-x: calc(var(--size) * (var(--frames) * -1 + 2));  
  background-position-y: calc(var(--size) * 0.02);  
  width: var(--size);  
  height: var(--size);  
}  
  
input:checked + .hearth {  
  animation: like 1s steps(calc(var(--frames) - 3));    
  animation-fill-mode: forwards;  
}  
  
@keyframes like {  
  0% {  
    background-position-x: 0;  
  }  
  100% {  
    background-position-x: calc(var(--size) * (var(--frames) * -1 + 3));  
  }  
}  
  
@media (hover: hover) {  
  .like:hover {  
    background-color: #E1255E15;  
    .hearth {  
      background-position-x: calc(var(--size) * (var(--frames) * -1 + 1));  
    }  
  }  
}

和普通图片如出一辙,通过background-image来控制背景SVG图片的顺序,对背景的横坐标进行侧移动画操作,只不过帧数提高到62帧:

See the Pen <a href="https://codepen.io/v3ucom/pen/MWGwXPv"> Untitled</a> by 刘悦的技术博客 (<a href="https://codepen.io/v3ucom">@v3ucom</a>) on <a href="https://codepen.io">CodePen</a>.

可以看到这里通过input:checked状态就可以触发@keyframes like横移动画,并不需要单独撰写JavaScript逻辑。

结语

三种动画特效实现方式各有千秋,但从可维护性以及成本控制角度上看,SCSS显然是最优解。

有关一颗红心,三手准备,分别基于图片(img)/SCSS(样式)/SVG动画实现动态拉轰的点赞按钮特效的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  3. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  4. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  5. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  6. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

  7. ruby - "public/protected/private"方法是如何实现的,我该如何模拟它? - 2

    在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定

  8. ruby-on-rails - Rails 单选按钮 - 模型中多列的一种选择 - 2

    我希望用户从一个模型的三个选项中选择一个。即我有一个模型视频,可以被评为正面/负面/未知目前我有三列bool值(pos/neg/unknown)。这是处理这种情况的最佳方式吗?为此,表单应该是什么样的?目前我有类似的东西但显然它允许多项选择,而我试图将它限制为只有一个..怎么办? 最佳答案 如果要使用字符串列,让我们说rating。然后在你的表单中:#...#...它只允许一个选择编辑完全相同但使用radio_button_tag: 关于ruby-on-rails-Rails单选按钮-模

  9. ruby - Jekyll 2.0 中的 SCSS 生成错误 - 2

    我的项目布局如下:-Project-css-import.scss-_sass/main.scssimport.scss的内容是:------@import"main.scss";我期望发生的是将main.scss导入到import.scss中,然后,import.scss将在生成的_site/目录中编译为import.css。相反,我收到以下错误Conversionerror:Therewasanerrorconverting'css/import.scss'.jekyll2.0.3|Error:InvalidCSSafter"-":expectednumberorfunction,

  10. ruby - 实现k最近邻需要哪些数据? - 2

    我目前有一个reddit克隆类型的网站。我正在尝试根据我的用户之前喜欢的帖子推荐帖子。看起来K最近邻或k均值是执行此操作的最佳方法。我似乎无法理解如何实际实现它。我看过一些数学公式(例如k表示维基百科页面),但它们对我来说并没有真正意义。有人可以推荐一些伪代码,或者可以查看的地方,以便我更好地了解如何执行此操作吗? 最佳答案 K最近邻(又名KNN)是一种分类算法。基本上,您采用包含N个项目的训练组并对它们进行分类。如何对它们进行分类完全取决于您的数据,以及您认为该数据的重要分类特征是什么。在您的示例中,这可能是帖子类别、谁发布了该项

随机推荐