[这个我能做]用多种方式实现小米新logo(内含两百万生成器)

2,108 阅读8分钟

最近小米的新logo可谓是引起了新一轮的吐槽和狂欢,这个价值两百万设计的logo来头不小,又是生命感又是超椭圆,虽然说是说知道“敲一榔头不值钱,知道往哪里敲最难得”,但是这个类似桌面主题图标的效果还是让一些网友吐槽“我上我也行”,那么,在四月一日这个可以随便做梦的日子里,就来试试怎么赚上几个200万吧
本文内容相当基础,所以只是图一乐用的
什么?想知道两百万生成器怎么做?直接跳转底部,我怎么会骗你呢
中间涉及的代码放在了这里:CodePen

摸鱼流

不忘初心,重回切图仔时代,管他什么兼容性浏览器差异,img就一个字,我只打一次 官网使用贴图解决

CSS流

使用圆角

新旧对比
生命之理,方圆之间,所以使用border-radius贯彻这种哲理是最佳之选,根据V站老哥的发现,新logo刚刚发布的时候,官网就是单纯在旧的基础上加了个圆角,这体现了张弛有度的人生哲学,妙哉妙哉
设计师不给素材我能咋办.gif

使用蒙版

根据设计师所说,新的图标的超椭圆来自于|X|^n+|y|^n=1这个公式,看来直接画出来的确有些难度,那么就在设计图的基础上使用蒙板解决吧
这次要使用的蒙板长这样:
Snipaste_2021-04-01_16-37-42.png mask是什么可以参见MDN,如果想要了解如何更好地使用,可以阅读奇妙的 CSS MASK mask是一个CSS属性,可以使用图片或者svg来对图片进行裁剪,也就是说,如果想要让图片可以裁剪出这样的边缘,可以使用mask-image来导入蒙板,然后设置mask-size使其贴合即可

#img-mask{
    position: relative;
  width: 100px;
  height: 100px;
  background-image: url("https://i.loli.net/2021/04/01/E8Rk27LPWNIbwBH.png");
  background-size:100% 100%;
  background-position: center;
  -webkit-mask-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAxIiBoZWlnaHQ9IjMwMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTUwLjUyNyAzMDAuMjY4Yy0yMy44MDktLjMtNDcuNTg4LS40OC03MS4wOTctNS40ODdhOTIuMjcgOTIuMjcgMCAwMS03My4wNzYtNzQuMjc2IDM3OS4xMTYgMzc5LjExNiAwIDAxLjA2LTEzOC41MzZjNy43NjYtNDAuOTAxIDMzLjcwNC02NS45NyA3NC4xNTUtNzQuOTY2QzExNi40OTMtLjg4MyAxNTIuOTI2LjU4NiAxODkuNDIgMi41NjVjMjAuNzUxIDEuMTQgNDAuODcxIDUuMDY4IDU5LjQ2MyAxNS4yMzMgMjQuNDk4IDEzLjQ5NCAzOC42NTIgMzQuNzU0IDQ0Ljc5OSA2MS41MDIgOC4wNjYgMzUuMjA0IDYuNjU3IDcxLjAwNyA1LjE4OCAxMDYuNzIxYTE2NC45NDIgMTY0Ljk0MiAwIDAxLTguNjk2IDQ4LjU0NyA4OC4wNCA4OC4wNCAwIDAxLTU1LjQ0NSA1NS45MjVjLTIxLjYyIDcuOTQ2LTQ0LjE2OSA4Ljk5NS02Ni44MzkgOS43NzUtNS43ODcuMTUtMTEuNTc0IDAtMTcuMzYyIDB6IiBmaWxsPSIjMDAwIi8+PC9zdmc+");
  -webkit-mask-size:100% 100%;
}

使用伪元素

为了节省大家的流量,我们还可以使用省流大师为大家提供高清又节省流量的体验,接下来试一下单纯使用CSS绘制中间的MI字吧,代码来自stevenlei
首先,来实现字母M的轮廓,之后再解决两条I。

Snipaste_2021-04-01_16-33-28.png
因为其类似矩形,因此可以使用border来实现。使用::before来在元素上面绘制,通过设置四条边的大小绘制拱门,然后针对右上角的弧度,使用border-top-right-radius单独设置,当然,左上角的直角也同理。

#div-box-shadow::before {
  content: '';
  display: block;
  position: absolute;
  width: 45px;
  height: 41px;
  top: 30px;
  left: 18px;
  
  box-sizing: border-box;
  
  border-color: #fff;
  border-style: solid;
  
  border-left-width: 10px;
  border-right-width: 10px;
  border-top-width: 8px;
  border-radius: 2px;
  border-top-right-radius: 10px;
  border-top-left-radius: 0px;
  border-bottom-width: 0;
}

接下来,实现两条直线。

Snipaste_2021-04-01_16-41-03.png
不过这两条直线长度不一,如何使用一个伪元素来表示?(毕竟::before已经用来画n了)我们把目光转向box-shadow
box-shadow介绍参见MDN,基本语法如下

/* x偏移量 | y偏移量 | 阴影颜色 */
box-shadow: 60px -16px teal;

/* x偏移量 | y偏移量 | 阴影模糊半径 | 阴影颜色 */
box-shadow: 10px 5px 5px black;

/* x偏移量 | y偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
box-shadow: 2px 2px 2px 1px rgba(0, 0, 0, 0.2);

/* 插页(阴影向内) | x偏移量 | y偏移量 | 阴影颜色 */
box-shadow: inset 5em 1em gold;

/* 任意数量的阴影,以逗号分隔 */
box-shadow: 3px 3px red, -1em 0 0.4em olive;

/* 全局关键字 */
box-shadow: inherit;
box-shadow: initial;
box-shadow: unset;

因此,我们设置宽高画出一个伪小方块后,就可以通过绘制多个无模糊的阴影(影分身),再设置偏移拼接出两个长方形

#div-box-shadow::after {
  content: '';
  display: block;

  position: absolute;
  width: 12px;
  height: 17px;
  top: 47px;
  left: 35px;
  background-color: #fff;
  
  border-radius: 2px;
  
  box-shadow: 0px 7px 0px 0px #fff, 
              35px -17px 0px 0px #fff,
    35px -4px 0px 0px #fff,
    35px 8px 0px 0px #fff;
}

合起来的效果如下

Snipaste_2021-04-01_16-48-54.png

SVG流

其实图片无论是在传输还是在显示上都是不太合适,对于这种简单的logo,让我们来试试使用svg进行绘制
svg的介绍参见MDN,想了解如何绘制svg元素中的path,可以阅读MDN-Paths。本质上就是不断地移动画笔进行连接,得到一个几何轮廓。对于外面的“超椭圆”,想到了两种方式进行实现。
首先,既然有了生成公式|X|^3+|y|^3=1,那么可以先计算出多个点进行拟合,然后依次连接,不过因为还是不够优雅,我们使用贝塞尔曲线来绘制,如果感兴趣的话,强烈推荐阅读一下这个介绍,下面摘抄一些关键部分

  • 控制点不总是在曲线上
  • 曲线的阶次等于控制点的数量减一。 对于两个点我们能得到一条线性曲线(直线),三个点 — 一条二阶曲线,四个点 — 一条三阶曲线。
  • 曲线总是在控制点的凸包内部 Snipaste_2021-04-01_17-00-20.png
    如何从控制点得到曲线?过程如下
  1. 绘制控制点。在上面的演示中,它们标有:1、2 和 3。

  2. 创建控制点 1 → 2 → 3 间的线段. 在上面的演示中它们是棕色的。

  3. 参数 t 从 0 to 1 变化。 在上面的演示中取值 0.05:循环遍历 0, 0.05, 0.1, 0.15, ... 0.95, 1。

  4. 对于每一个 t 的取值:在每一个棕色线段上我们取一个点,这个点距起点的距离按比例 t 取值。由于有两条线段,我们能得到两个点。

    例如,当 t=0 — 所有点都在线段起点处,当 t=0.25 — 点到起点的距离为线段长度的 25%,当 t=0.5 — 50%(中间),当 t=1 — 线段终点。 连接这些点,下面这张图中连好的线被绘制成蓝色。

    Snipaste_2021-04-01_17-05-08.png

  5. 现在在蓝色线段上取一个点,距离比例取相同数值的 t。也就是说,当 t=0.25(左图)时,我们取到的点位于线段的左 1/4 终点处,当 t=0.5(右图)时 — 线段中间。在上图中这一点是红色的。

  6. 随着 t 从 0 to 1 变化,每一个 t 的值都会添加一个点到曲线上。这些点的集合就形成的贝塞尔曲线。它在上面的图中是红色的,并且是抛物线状的。

回到前端,如果要绘制简单圆弧,我们要创建path元素,然后使用二次贝塞尔曲线指令q来绘制,前两项是相对于前一个点的位置差,因此绘制第一个q之前,需要移动到一个起始点。

 Q x1 y1, x y
 (or)
 q dx1 dy1, dx dy

quadratic_bézier_with_grid.png
代码如下,来自:hyrious

  <svg viewBox="0 0 100 100" style="width: 100px; height: 100px">
        <path fill="#ff6700" d="
            M 50,10
            q 40,0 40,40
            q 0,40 -40,40
            q -40,0 -40,-40
            q 0,-40 40,-40
        " />
        <path fill="transparent" stroke="white" stroke-width="6" d="
            M 31,36
            v 30
            M 31,39
            h 17
            a 8,8,90,0,1,8,8
            v 19
            M 43.5,48
            v 18
            M 68,36
            v 30
        " />
    </svg>

其中,M 50,10就是移动到50,10处,q 40,0 40,40就是在前一个点的基础上在x相差40px得到的点作为控制点,而40,40作为这个曲线的终点。因此可以绘制出一个50,10->40,40,控制点为90,10的弧线,以此类推,至于其他绘制直线的指令就用到的时候再查吧
效果如下(还是相当简洁又好康的):
Snipaste_2021-04-01_17-28-28.png

CANVAS流

这个还是非常粗暴的,绘图和导出在此按下不表,如何注入灵魂,可以使用 ctx.globalCompositeOperation 来设置图片叠加绘制的逻辑,例如使用

ctx.globalCompositeOperation = "destination-in";

就可以在旧图层上只保留新图层中不为透明像素对应位置的像素,类似于遮罩,除此之外还有其他各种选项,可以实现多钟混合效果。
代码如下:

let c,ctx;
function handlePicUpload(e){
    const file  = e.files[0];//当上传了图片之后
    url = URL.createObjectURL(file);
    console.log(url)//blob:
    let img = new Image();
    c=document.getElementById("canvas-logo");//这是一个canvas
    ctx=c.getContext("2d");
	img.onload = function(){
		ctx.drawImage(img,0,0,100,100);//先绘制原来的图像
        ctx.globalCompositeOperation = "destination-in";//修改为遮罩模式
        let mask = document.getElementById("mask");//获得价值两百万的遮罩
        ctx.drawImage(mask,0,0,100,100);
        let exportBtn = document.getElementById("export-btn");
        exportBtn.disabled = false;
	}
    img.src=url;
    e.value='';
}
function handlePicExport(){//将画布内容导出
    console.log(c)
    c.toBlob(function(blob) {
        console.log(blob)
      let link = document.createElement('a');
      link.download = 'two_million.png';
      link.href = URL.createObjectURL(blob);
      link.click();
      URL.revokeObjectURL(link.href);//销毁blob,释放内存
    }, 'image/png');
}

大功告成!现在可以上传自己的头像,然后让它瞬间身价百万,这不就是大家都喜欢的两百万生成器吗(不是
不过话说回来如果蹭热度做一个小程序,就是给头像一键上个200w的Buff,说不定会意外地流行呢...