html5 Canvas画图

画布画笔

虽然大家都称Canvashtml5的新标签,看起来好像Canvas属于html语言的新知识,但其实Canvas画图是通过javascript来做的。所以,如果你想学习Canvas画图,你必须要有Javascript基础。

另外,画图嘛,总有一些图像方面的术语和知识点,所以如果你有过做图或美工经验,学习Canvas会更容易。

Canvas,意为画布也。而Html5中的Canvas也真的跟现实生活中的画布非常相似。所以,把他看成一块实实在在的画布可以加快理解。

画布

用canvas作画,首先,你需要有一块“画布”。如果你的书架里面没有画布,你可以买一卷回来放进去。当然,在网页里面我们不需要花钱买,直接写一个canvas即可,类似:

代码如下:

你的浏览器不支持canvas 

其中标签里面的文字是给不支持canvas的浏览器看的,支持的永远看不到。

这个画布的特性有必要说一下,他有两个原生的属性,即width和height.同时,因为他也是一个html元素,所以他也可以使用css来定义width和height,但是,千万要注意:他自身的宽高和通过css定义的宽高是不一样的!

我们用JS来改变Canvas的宽高,是这样的:

代码如下:

canvas.width= 400 
canvas.height = 300 

但用JS通过操作CSS来改变Canvas的宽高,则是这样:

代码如下:

canvas.style.width = '400px' 
canvas.style.height = '300px' 

看得出来,语法上区别是很明显的。实际上区别更明显。

他们的区别是什么?

比如一块宽1000的画布,你在画布左侧画了一条竖线,宽100像素。此时你把画布自身的width设为500,相当于把画布的右半边咔嚓掉了,但此时那竖线的宽度还是100。

但如果你通过CSS来把画布的宽度变成500,那就相当于把画布由1000挤压到500,所以竖线的宽度变成了50.

(这只是理论情况,实际上设置canvas的宽度时,他会清空掉已画出来的内容。。)

Canvas自身的宽高就是画布本身的属性,而css给他的宽高则可以看作是缩放,如果你缩放的太过随意,那么画布上的图形可能变得你自己都认不出来。

所以有个建议:除非特殊情况,一定不要用css来定义Canvas的宽高。

画布有了,现在我们把他拿出来:

代码如下:

var cvs = document.getElementById('cvs'); 

看,跟获取其他元素的办法一模一样。

画笔

现在画布已经有了,想往上面涂鸦,当然还需要一只笔。canvas取得笔的方法如下:

var ctx = cvs.getContext('2d');其中getContext方法就是用来拿笔的,但这里还有个参数:2d,这是什么意思呢?这个可以看作是画笔的种类。

既然有2D,那么就会有3d了?以后估计会有,但现在没有。所以我们先用这只2d的笔吧。

那么我们可以多放几只笔来备用吗?答案是不能。

我要问一个问题:你画图的时候是同时用几只笔呢?相信99.9%的人都是只能用一只,虽然有些武林高手比如小龙女之类的可以两只手同时画,但这对一般人来说很不现实,是不是?

所以现在你可以感到欣慰了,因为html5的canvas标签也只支持同时用一支笔!

有的写JS写的比较熟的同学可能会想耍个小聪明:我用前面获取画笔的方法多整几只笔出来,不就行了?!

比如:

代码如下:

var con = cvs.getContext('2d'); 
var ctx = cvs.getContext('2d'); 

哈哈哈哈,好像成功了,在没测试之前我也是这么想的,但,其实这只是一个幻觉!

因为我发现,我把其中一支笔蘸上红墨水,另外一支笔也自动蘸上了红墨水!因为两支笔是一体的!fuck。

如果你需要画出不同的颜色,办法就是把这只唯一的“笔”不停的蘸上新颜色。

这其实不是一个优点,是个缺陷,以后你会体会到的。

坐标

2d世界,就是平面,在一个平面上确定一个点,需要两个值,x坐标和y坐标。这是一个很重要的基础概念。

canvas的原点是左上角,跟flash一样。但蛋疼的是数学中的原点是左下角。这个…只能说习惯就好

一些画图的基本常识

首先你需要知道,怎样的坐标变化会画出什么线?比如,x坐标变大而y坐标不变,则能画出一条横线;y坐标变化而x坐标不变,则是一条竖线。

当然,还有斜线,左斜线右斜线什么的,如果能对照图片,大部分人都能一看即懂;只是用代码画起来就比较郁闷了,只能靠逻辑思维想出来。

如果你现在感觉对线条一片混沌,也不用担心,在学习的过程中自然会理解。

其他

canvas有一个和现实的画布不一样的特点就是,他默认是透明的,没有背景色。这在大部分时候非常重要。

画线条

在学画画的时候,线条是最基本的了,而线条的连接可以组成任何图形。在Canvas中也是如此。

在开始之前我们先拿出画布和画笔:

代码如下:

var cvs = document.getElementById('cvs'); //画布 
var ctx = cvs.getContext('2d'); // 画笔 

我们画画的时候,落笔点是不固定的,随时都会变。canvas虽然不是通过手来决定落笔点,但也有一个方法,就是moveTo。moveTo的作用相当于把笔尖提离画布,然后移动到指定的点(即坐标)。

代码如下:

ctx.moveTo(x,y) 

此过程中不会画出任何图形,相当于你提着笔在画布上晃来晃去。

但晃来晃去是没用的,我们必须开始画。先画最简单的:直线

画直线的方法即lineTo,他的参数和moveTo一样的,都是一个点。

ctx.lineTo(x,y)当然,你画线的时候,落笔点也跟着移动了,所以lineTo之后落笔点就变成了他的目标点了。

代码如下:

ctx.moveTo(100,100); 
ctx.lineTo(200,100); 

此时你刷新网页,会发现画布上没有预想中的线,什么也没有。因为我们还少了一个步骤.lineTo其实是画的一条“路径”,本身是不可见的。如果要让他显示出来,我们必须对他进行“画”的操作。

用过PS的同学,肯定能知道图形的两种模式,一种是填充,另一种是描边。现在我们已经画了一条线,相当于PS中勾了一条路径,此时给路径描一下边,就能显示出图形了。

canvas描边的方法是stroke().现在让我们把代码补全:

代码如下:

ctx.moveTo(100,100); 
ctx.lineTo(200,100); 

ctx.stroke();此时刷新,就能看到一条线了。当然,你也可以连续绘制出几百条路径,再执行描边动作,即可一下画出几百条线。现在我们来用4条线画一个矩形:

代码如下:

ctx.moveTo(100,100); 
ctx.lineTo(200,100); 
ctx.lineTo(200,200); 
ctx.lineTo(100,200); 
ctx.lineTo(100,100); 
ctx.stroke(); 

这里我们就是先绘出全部路径,再一次性描边。

——–作者的抱怨:Canvas绘图有个不好的地方就是:基本靠猜,很不直观。

严重提示:canvas的绘图过程(即填充与描边)是非常消耗资源的,如果想节省系统资源提高效率,最好是绘制好所有路径,再一次性填充或描边图形。

由上面的图形我们可以看出,默认的线条粗细是1px,而线条颜色是黑色。当然我们可以设置他们,但奇怪的是设置线条宽度是lineWidth,而设置线条样式的却叫strokeStyle,为什么不是lineStyle呢?我也不知道。:

代码如下:

ctx.lineWidth = 10; 
ctx.strokeStyle = 'rgba(255,0,0,0.5)'; 

上面的代码把线条宽度设置成了10px,线条颜色变成了半透明的红色。

如图1,刷新一下,好像有点不对呢!怎么左上角缺了一小块似得?这不是错觉。原因嘛要从canvas的线条绘制方式说起。

问题:如果我画的矩形路径是宽高都是100,此时我的边线宽度是10px,那么这个描了边的矩形整体宽高是多少?是100+10*2=120吗?

如果边线是完全描在路径外侧的话,那么就是120。但Canvas不是。Canvas中的线条都有一条“中线”,这条中线位于line的绝对中间,线条的笔画以中线向两侧扩展。比如你的线条宽度是1,那么中线就在0.5的位置;宽度是5,那么中线就是在2.5。canvas的图形在描边的时候,都是路径与line的中线贴合,然后描边。如图2:

所以,描边的时候线的一半在外侧,一半在内侧,即上面的矩形的整体宽度是100+(10/2)*2,等于110.

也正是因为这个原因,左上角出现缺角就理所当然了。因为这里没人画。

但为什么其余的角没有出现缺口呢?看你的图不是4个角都有缺口吗?

那是因为,我画线的过程中没有把画笔“提起来”,画笔是连续的,即没有moveTo过。不信我们现在来moveTo一下:

代码如下:

ctx.moveTo(100,100); 
ctx.lineTo(200,100); 
ctx.moveTo(200,100); //注意这里 
ctx.lineTo(200,200); 
ctx.lineTo(100,200); 
ctx.lineTo(100,100); 
ctx.lineWidth = 10; 
ctx.strokeStyle = 'rgba(255,0,0,0.5)'; 
ctx.stroke(); 

我们再画第二条线之前moveTo了一下,而且moveTo连坐标都没变,还是那个点,但刷新后图形变成了这个样子[图3]:

明白了?因为我们把笔提起来了。

现在我们删掉moveTo,不要纠结他了,我们来思考一下如何把左上角那个缺角给补上?

首先问个问题,我们的路径闭合了吗?这不是废话么,我们不是已经把路径绕回原点了么?当然算是闭合了!

错!这样只是让路径最后一个点和起点重合了而已,路径本身却没有闭合!

Canvas怎么闭合路径?用closePath().

复制代码

代码如下:

ctx.moveTo(100,100); 
ctx.lineTo(200,100); 
ctx.lineTo(200,200); 
ctx.lineTo(100,200); 
ctx.lineTo(100,100); 
ctx.closePath();//闭合路径 
ctx.lineWidth = 10; 
ctx.strokeStyle = 'rgba(255,0,0,0.5)'; 
ctx.stroke(); 

此时刷新,就是一个完美的正方形了。图4:

无论我们把线条改到多粗————越粗越有人喜欢是吧?————这个四方形的四个角都是规矩的直角,不会出现圆滑的情况。圆滑的角是什么情况?请看PS中的四方形描边,图5:

看到了吧,越粗的边线,他的角的圆弧越大。

如果我想Canvas里面的边线也和PS这种一样,有没有办法呢?当然有,就是lineJoin属性。

lineJoin,意思即线的交汇处,有3个属性:miter(默认,尖角),bevel(斜角),round(圆角),如图6:

毫无疑问我们一下就能明白我们的矩形用的是尖角,所以试着把他改成圆角看看:

图形变成了这样,图7:

有点像PS的了吧?

另外,通过前面图我们了解到,Canvas的线条两端是平的,可不可以改呢?毕竟平的不好看。

也是可以的,即lineCap属性,这个就是定义线条的端点。lineCap有3个值:butt(平,默认),round(圆),square(方),如图8

看图就能发现,其实平头跟方头是一样的,区别只是平头没有伸出去那么一截。圆头和方头都会伸出去一截,这一节是多长呢?就是线条宽度的一半。

你有没有想到什么?哈哈,前面的闭合路径的问题,如果我们把lineCap设为方头,效果也是一样的!

但为了保险起见,我们还是要把路径闭合了,切记!

我还要提醒一下:闭合的路径没有端点!所以闭合的路径上看不到端点的样式。

另外:lineCap与lineJoin有点相似,注意不要搞混。

都2024了,别拿IE当借口了,快去学这些css 新属性!!

点击关注公众号,“技术干货”及时达!

前言❝

记得刚入行前端的时候,当时因为刚好学过 vmin vmax ,就直接在我们的移动端项目中用,结果被老大喷,说你写项目能不能别搞这些花里胡哨的,就用点朴实无华的行不行,我当时心里是 :*********

最近接手了一个项目,有一个 4乘4 的网格布局,我用了flex 去布局,然后又被同事嘲讽,都什么年代了, 这种布局明显是用 grid 更好啊,我当时心里是: ************

好好好!!! 好坏赖话都让你们说了是吧!!

言归正传,毕竟都 2024 年了 该学点新的 css 属性了,要不然过几年这些属性通用了你都没听说过。

@scope

你写了这么长时间项目 ,对于css 样式隔离应该不陌生, 比如Vue Scoped Styles react 中的 css in js 或者是 CSS Modules ,但是原生css 样式隔离方案应该没接触过吧 ,@scope 就是原生css 的样式隔离方案。

@scope 语法规则

@scope (scope root) to (scope limit) {
  rulesets
}

大家可以自行去 MDN 、csswg 去查看详细规则,他的主要用法有三种:

用法一

只用 第一个参数

html代码:

<div class="box">
   <div class="red">这里是红色div>
div>
<div class="red">这里不是红色div>

css代码:

 <style>
      @scope (.box) {
        .red {
          background-color: red;
        }
      }
 style>

运行结果:

我们可以看到, 类名为 .red 的只有在 scope 中才会显示为红色,说明样式隔离成功了

用法二

可以看到 还有个 to 包裹的类名,那他的含义是什么呢?

HTML代码

 <div class="box">
      <div class="red">这里是红色div>
      <div class="filter">
        <div class="red">这里不是红色,因为被过滤了div>
        <div class="red-box">
          <div class="red">即使是子组件也不是红色,因为被过滤了div>
        div>
      div>
      <div class="red">这里是红色div>
    div>
 <div class="red">这里不是红色div>

css代码

    <style>
      @scope (.box) to (.filter) {
        .red {
          background-color: red;
        }
      }
    style>

运行结果

可以看到 to中的 .filter 下的所有的red 也不是红色 其实就是一种过滤效果,你也可以理解为这是一种甜甜圈的结构 只有.box 中和 .filter 以外的元素才能被选中 。

要注意 @scope 的用法是 @scope(空格)(元素类名)(空格)to(空格)(元素类名) to 后面的空格不要忘记了否则是不生效的

用法三

什么参数都不加 @sope

    <div class="box">
      <style>
        @scope {
          .red {
            background-color: red;
          }
        }
      
style>
      <div class="red">这里是红色div>
    div>
    <div class="red">这里不是红色div>

运行结果

作为包含在 HTML 元素中的内联样式,在这种情况下,前奏将被省略,并且包含的规则集会自动将范围限定为 元素 的封闭父元素 ,意思就是你写在了 html 结构中你就可以把 scope root给省略了,当然你加上也是没问题的。

@container 容器查询

这个设计几乎可以改变我们对"响应式设计"含义的看法,以前我们都是使用 @media 媒体查询 通过viewport 来调整样式的,但是这种太过于宏观,现在出了 @container 你可以根据父容器大小来设置元素的样式。

 <style>
      .demo {
        width200px;
        height200px;
        background: red;
        container-name: sidebar;
        container-type: inline-size;
      }

      @container sidebar (width > 300px) {
        a {
          background: green;
        }
      }
    
style>
  head>
  <body>
    <div id="app">
      <div class="demo">
        <a>我的背景色会随着demo元素的宽度而变化a>
      div>
    div>
  body>

主要的用法就是 @container container-name (条件) { 样式表 }

你可以给需要增加容器查询的元素增加 container-name 和 container-type 如果你的全局只有一个@container 那你就可以 省略container-name ,对于container-type 总共有以下几个属性:

:has :where :is:has 伪类选择器 --选中父级

在css 中,我们正常想选择某些东西 我们基本上是用后代选择器

div pclolor:red }

但是我们有时候想 从子选中父,就可以用:has

div:has(p)color:red }

上面的意思表示将所有的含有p 标签的div 标签的颜色变成红色

应用场景:

我们公司就有位同事 二次封装了 form 表单 但是对于插槽功能会有段这个结构

你说我要选中 这个没有类名的div 我该如何选中 ,而且他还是封装起来的div 我无法动态给这个div加类名

有的人就说了 你可以用 .elform-item_content>div 啊 ,可是这样的话所有的这种结构都会被选中,但是我想要的是只有 含有.chooseReviewIptr类名的才能被选中。

所以我们就可以这么写:

 :deep(.el-form-item__content) div:has(.chooseReviewIpt) {
    width100%;
  }

当然 :has 还有其他很多种用法,他的出现确实惊艳了众人, MDN

:where伪类选择器 -- 匹配规则

平时你可能会写出这种代码

header a:hover,
main a:hover,
footer a:hover {
  color: green;
  text-decoration: underline;
}

但是我们用了:where 就可以精简代码

:where(headermainfootera:hover {
  color: red;
  text-decoration: underline;
}

这个伪类代表:选择所有能被该选择器列表中任何一条规则选中的元素 MDN

:is伪类选择器 -- 匹配规则

:is 用法和 :where 运行方式几乎一样 主要的区别在于 :where 优先级总为 0 ,:is的优先级取决于最特定参数的优先级 MDN

css嵌套

sass 、less 嵌套 大家应该都很熟悉了,css 嵌套目前浏览器也是支持了的

使用方法:

    <div class="box">
      <div class="red">这里是红色div>
      <span>红色span>
    div>
    <div class="red">这里不是红色div>

      div {
        color: red;
        span {
          color: green;
        }
      }

      .box {
        .red {
          color: red;
        }
      }

      .box {
        & .red {
          color: red;
        }
      }

注意点

但是你得注意到 原生css 嵌套 和sass less 的嵌套规则还是有所区别的

.card {
  &--header {
    /* is not equal to ".card--header" */
  }
}

在css 中是不支持这种写法的 ,他会被会忽略掉 ,但是在sass 中是可以的,不过后续css官方会不会支持这种写法还不太清楚

nth-of

如果给你一下需求,你会怎么去实现 ?

下面是一个不可以更改的结构 比如从一个被封装的组件 你无法修改他的结构也不能动态加类名,你改如何用css去选中 第二个类名为.p2 的标签

    <h1 class="h1">标题1h1>
    <h1 class="h2">标题2h1>
    <p class="p1">段落1p>
    <p class="p1">段落1p>
    <p class="p2">段落2p>
    <p class="p2">段落2p>  选中这个
    <p class="p2">段落2p>
    <p class="p3">段落3p>

你可能会第一反应 是nth-child 和 nth-of-type,那我们来讨论下:

1、nth-child

.p2:nth-child(2){
    color:red
}

你会发现根本选不中 因为上面这个选择器的含义是==>父级下:nth-child(2) 第二个子元素 并且类名为.p2的元素

所以这样是选不中的。

2、:nth-of-type

.p2:nth-of-type(2){
    color:red
}

这种也是选不中的,因为 上面的含义是:

1)父级下 :nth-of-type(2) 每种类型的第二个元素 并且类名为.p2的才能被选中,

2)也就是说父级下总共有h1 、p 两种元素 ,

3)h1元素 的第二个就是 标题2 , p 元素的第二个就是 第二个段落一 ,

4) 被选中的就是这两个元素的类名分别为 .h2 和 .p1 ,不符合 类名为.p2 的条件所以不被选中

放在以前我们是很不好通过css 去选中这个结构的 我们八成会通过 js 去选中 不过现在出现了 of 关键词了, 可以很好解决我们这个问题。

3、nth-child 中的 of 关键词

你就可以这么写

:nth-child(2 of .p2){
  color: red
}

上面的意思就是

1)首先筛选 父级下所有的类名为 .p2的

2)然后在这个筛选结果中 选择第二个

这样就很轻松的选中我们需要选中的了。

text-wrap: 新增的两个属性 balance 与 prettytext-wrap: balance

平时我们开发过程中 很可能会遇到这种情况

一行文字放不下 会折行,会给用户一种错觉 coffee 是突出的效果,但是从UI的角度更想要的是这种效果:

我们之前大多都是两种解决方案 一种是在 every 后面加个

换行,或者是用两个p 标签分割这个结构 导致他们不在同一行。

但是我们可以感受到这种都是去通过打断句子的方式,所以官方就出了新的属性text-wrap:balance,

加了这个属性之后浏览器会自动计算单词的数量,并将他们平分在两行

pretty

也有可能你想要的不是均分单词 仅仅是不想让最后一行出现单独的单词,这个时候你就可以用text-wap:blance的方式 ,他就会让最后一行多几个单词显示。

原生的popover弹出层

很多时候我们是不是都会遇到有 弹出层popover的需求,大多时候我们要么用组件库中的要么自己手写,现在你不需要了,因为浏览器已经支持原生popover了

    
<button class="menu-btn" popovertarget="menu">
      <img src="https://upload.wikimedia.org/wikipedia/commons/b/b2/Hamburger_icon.svg" alt="" />
    button>
    <div popover role="menu" id="menu">
      <button class="close-btn" popovertarget="menu" popovertargetaction="hide">
        <span>span>
        <span class="sr-only">Closespan>
      button>
      <ul>
        <li><a href="#">Typographya>li>
        <li><a href="#">Foundationsa>li>
        <li><a href="#">Colora>li>
        <li><a href="#">Interactionsa>li>
        <li><a href="#">Componentsa>li>
        <li><a href="#">Responsivea>li>
      ul>
    div>

用法就是 你得给需要做弹框的容器增加 popover 属性 并且增加id 属性 后使用 popovertarget="my-popover" 将其 id 属性与调用按钮相关联。这会让我们更加轻松的实现弹出层,不用去写js 去控制隐藏和显示了,这个大概以后会影响到很多组件库重写弹出层的逻辑。

点击关注公众号,“技术干货”及时达!

本站内容来自用户投稿,如果侵犯了您的权利,请与我们联系删除。联系邮箱:835971066@qq.com

本文链接:http://news.xiuzhanwang.com/post/974.html

发表评论

评论列表

还没有评论,快来说点什么吧~

友情链接: