整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

如何通过css格子布局一个简单的页面

文链接: https://getflywheel.com/layout/css-grid-layouts-how-to/

栅格布局的思想起源源自于印刷设计。栅格是用来将设计元素精确定位到页面上的的测量工具。这种想法经常被用在网页上来进行内容组织和统一,提升用户的视觉体验。

网页设计刚起步的时候,设计和布局都是是相当简单的, 通常包含头部,侧边栏,内容区域和页脚。现在,随着网络的演变,网页的布局也变得更加复杂,做网页设计师的人也随之增加。我们经常需要大量的内容区域,响应式设计,多页面模板设计,以及许多其他的。浮动和定位在实现这些设计的时候,是必不可少的。但浮动听起来简单,实际操作起来却很棘手。

但接下来,我们会介绍一种简单的设计布局。随着CSS栅格布局的不断发展,成为设计师也会变得越来越容易。

CSS栅格兼容性

作为一名设计师,需要了解网页设计的未来。CSS栅格布局将改变现有规则,为设计师处理头痛了许多年的定位。虽然目前还不是主流的做法,但是这是一件值得期待的事情。

在我们真的深入了解栅格布局之前,要强调的一件事,浏览器并不普遍支持, 希望这种工作方式在未来可以得到越来越多的浏览器支持。不过, 好消息是, 您可以轻松地尝试使用CSS栅格布局,以及了解它是如何工作的。

在使用示例之前,请你确保你的浏览器支持。目前只有Internet Explorer 10+和 edge 支持。其他的浏览器通过一些手段也可以浏览,但因为它不是官网支持,所以你只能是不断的去尝试。(If you view the Can I Use documentation on CSS Grid Layouts, at the time of this post, you will notice little flag indicators. These show that you will need to be in “flag mode.”)如果你边使用Can I Use来查看栅格布局的兼容性,边看这篇文章,那么你就可以注意到每个细节的不同。

当您在测试栅格布局的时候,你需要做几件事情帮助你正确地看到布局。使用Chrome查看,你需要启用“实验性网络平台功能”。如何启用呢?在Chrome 浏览器中打开chrome://flags 这个地址。当url 链接chrome://flags加载完毕之后,向下滚动页面,找到该选项,设置为“启用实验性网络平台功能”。

火狐也允许您查看栅格布局,通过“layout.css.grid.enabled”参数设置。开启方法类似于Chrome浏览器的说明。在Firefox浏览器中URL输入 about:config。向下滚动页面,设置为启用 “layout.css.grid.enabled” 。

如果你想马上开始使用CSS栅格布局,对于不支持它的浏览器还有一个变通方案。如果你熟悉polyfills的想法,那已经有解决方案了。如果你不熟悉polyfills,可以利用浏览器后退,利用JavaScript的力量,允许现代的浏览器功能(例如CSS栅格布局)在旧的浏览器运行。

Polyfills超出本教程范围,但随着越来越多的设计师开始使用这项技术,更多的polyfill 技术将会涌现。如果你准备尝试,这里是一个推荐的 polyfill option。请务必阅读作者的文档,了解有关如何使用它的详细信息。

那么,在承诺100%使用CSS栅格布局之前,要确保使用的生产代码,做一些深入的测试。

CSS栅格布局基本知识

通过利用CSS,栅格布局将有助于您的网页内容的呈现。这里有一篇相对较新的定义的属性的CSS栅格布局规范 。这是学习栅格设计的一个很好的资源。CSS栅格设计有助于简化的东西,使创建布局更加容易。想象一下,栅格作为一种结构,尺寸可以被定义。

栅格的组成

行(lines)

在上图中,有五条垂直线和三条水平线。线从1开始编号。示例中,垂直线从左至右,这取决于书写方向。如果书写方向是由右至左,顺序就颠倒过来。可以给线起名(可选),方便在CSS中引用。

轨道(tracks)

轨道是两条平行线之间的空间。在图中,有四个垂直轨道和两个水平的轨道。这是线和轨道的共同结果。 线是记录内容的起点和终点。轨道是内容真实存在的位置。

单元格(cells)

单元格是水平和垂直轨道的相交处。图中有八个单元格。

面(areas)

单元格指定面的时候发挥作用。面是矩形形状,可以跨越多个单元格。像线一样,面也可以任意命名。如在图中的几个标签:“A”,“B”,和“C”。

创建栅格布局

用老方格纸,布局之前,先勾勒轮廓。

HTML栅格

<div class="container">

<div class="grid header">Header</div>

<div class="grid sidebar">Sidebar</div>

<div class="grid content">Main Content</div>

<div class="grid extra">Extra Content</div>

<div class="grid footer">Footer</div>

</div>

容器Container是非常重要的。容器内是用于显示网站的不同的内容块。内容块的顺序并不重要。接下来,我们将使用CSS将它们按照我们的布局显示。

CSS样式

HTML完成后,我们来写CSS。给container设置display:grid 或者 display:inline-grid. 如果你希望设置块级元素,那使用 display:grid ; 如果你希望设置成内联元素, 那使用display:inline-grid。想了解更多细节,可以查看文档

.container {

display: grid;

grid-template-columns: 0.25fr 15px 0.75fr;

grid-template-rows: auto 25px auto 25px auto 25px auto;

}

.grid {

background-color: #444;

color: #fff;

padding: 25px;

font-size: 3rem;

}

grid-template-columns和grid-template-rows属性用于指定行和列的宽度。这个布局定义了五列。15px是两个元素之间的间距。第三列占用0.25份的剩余空间。同样地,第五列占用0.75份的剩余空间。(疑问: 图中根本没有第五列啊,感觉作者写错了)

There are responsive customizations that can be made, but this is a great step to take prior to that. It may seem like using pixel measurements would be limiting, however, using auto for the first row in grid-template-rows allows the row to expand as necessary based on the content inside it. The 25px row acts as a gutter.

对于响应式布局这个规范是很便利的,如果使用像素,则会被限定死。第一行使用grid-template-rows来表示,随着内容需要的变化而变化。设置padding 成 25 像素,与头部留有间隙。

元素看起来很紧凑,但再加一些规范,元素将初具规模。

这个例子先放置的头部,但元素位置可以按您喜欢的任意顺序摆放。如果你想从页脚开始,也可以。

我们先从头部开始,从列1开始到列4结束,从行1开始到行2结束,CSS看起来就像这样:

.header {

grid-column-start: 1;

grid-column-end: 4;

grid-row-start: 1;

grid-row-end: 2;

}

您可能会注意到侧边栏被压住了,我们无法看到它。我们需要重新排列一下。在这种布局,通过行的位置进行排列。以行作为标准。头部占一行和二行的位置,侧边栏,从三行开始, 到六行结束。 头部到第二行结束,侧边栏从第三行开始正好可以显示到头部下面。要查看示例,请参见该项目Codepen。

我们使用grid-column-start指定一个元素起始垂直线。在本例中,它将被设置为3。grid-column-end表示一个元素的结束垂直线。在这种情况下,这个属性就等于四。其他行值也用同样的方式设置。侧边栏的位置是存在的,它只是覆盖的内容区。

.sidebar {

grid-column-start: 1;

grid-column-end: 2;

grid-row-start: 3;

grid-row-end: 6;

background: #a0c263;

}

主要内容在第三列开始,第四列结束。侧边栏和内容区域的顶部对齐,所以他们都从grid-row-start第三行开始。你可能想让内容栏比侧边栏高很多。通过设置容器上的高度,假如400像素,这个时候,它就会比其它元素高很多。

.content {

grid-column-start: 3;

grid-column-end: 4;

grid-row-start: 3;

grid-row-end: 4;

background: #f5c531;

height: 400px;

}

最后两个内容区域是额外内容区域和页脚。

.extra {

grid-column-start: 3;

grid-column-end: 4;

grid-row-start: 5;

grid-row-end: 6;

background: #898989;

}

.footer {

grid-column-start: 1;

grid-column-end: 4;

grid-row-start: 7;

grid-row-end: 8;

background: #FFA500;

}

响应式优势

布局已经创建好了,似乎很像一个“桌面”。那么平板电脑和移动设备怎么显示?CSS栅格布局加上媒体查询可以适应不同的屏幕尺寸。真正酷的是,你可以在这些不同的媒体查询范围里,改变内容区域。作为一个设计师,这意味着你选择什么是最适合你的布局在不同的断点。例如,如果你想要将“次要内容”被放在“内容”区域之上,可以指定正确的行和列。

/* For mobile phones: */

.header {

grid-column-start: 1;

grid-column-end: 4;

grid-row-start: 1;

grid-row-end: 2;

}

.extra {

grid-column-start: 1;

grid-column-end: 4;

grid-row-start: 3;

grid-row-end: 4;

}

.content {

grid-column-start: 1;

grid-column-end: 4;

grid-row-start: 5;

grid-row-end: 6;

background: #f5c531;

height: 400px;

}

通过设置成列1开始,列4结束,来设置成内容全宽。将“次要内容”显示在了“内容”之上。

CSS栅格布局是一种新型的布局方式。正如你所看到的,这种方法很容易创建一个简单的页面布局去运行。上面这个简单的例子也可以为如何创建更复杂的布局打下良好的基础。假如这个技术获得普及,在设计各种设备和尺寸,布局大小自定义的时候,这个技术会是一个优势。

问切 wenqie(.cn),是切图网旗下关注用户体验,专注H5移动适配的品牌网站。

文来源于:程序员成长指北;作者:去伪存真

如有侵权,联系删除


背景


最近在项目中要实现一个拖拽头像的移动效果,一直对JS Dom拖拽这一块不太熟悉,甚至在网上找一个示例,都看得云里雾里的,发现遇到最大的拦路虎就是JS Dom各种各样的距离,让人头晕眼花,看到一个距离属性,大脑中的印象极其模糊,如同有一团雾一样,不知其确切含义。果然是基础不牢,地动山摇。今天决心夯实一下基础,亲自动手验证一遍dom各种距离的含义。


JS Dom各种距离释义


下面我们进入正题, 笔者不善于画图, 主要是借助浏览器开发者工具,通过获取的数值给大家说明一下各种距离的区别。


第一个发现 window.devicePixelRatio 的存在


本打算用截图软件丈量尺寸,结果发现截图软件显示的屏幕宽度与浏览器开发者工具获取的宽度不一致,这是为什么呢?


  • 截图软件显示的屏幕宽度是1920



  • window.screen.width显示的屏幕宽度是1536



这是怎么回事?原来在PC端,也存在一个设备像素比的概念。它告诉浏览器一个css像素应该使用多少个物理像素来绘制。要说设备像素比,得先说一下像素和分辨率这两个概念。


  • 像素 屏幕中最小的色块,每个色块称之为一个像素(Pixel)



  • 分辨率 分辨率=屏幕水平方向的像素点数 * 屏幕垂直方向的像素点数; 另外说一下,关于分辨率有多种定义,可以细分为显示分辨率[1]、图像分辨率[2]、打印分辨率[3]和扫描分辨率[4]等,此处是指显示分辨率。



  • 设备像素比


设备像素比的定义是:


window.devicePixelRatio =显示设备物理像素分辨率显示设备CSS像素分辨率\frac{显示设备物理像素分辨率}{显示设备CSS像素分辨率}显示设备CSS像素分辨率显示设备物理像素分辨率


根据设备像素比的定义, 如果知道显示设备横向的css像素值,根据上面的公式,就能计算出显示设备横向的物理像素值。


显示设备宽度物理像素值 = window.screen.width * window.devicePixelRatio;


设备像素比在我的笔记本电脑上显示的数值是1.25, 代表一个css逻辑像素对应着1.25个物理像素。



我前面的公式计算了一下,与截图软件显示的像素数值一致。这也反过来说明,截图软件显示的是物理像素值。



  • window.devicePixelRatio 是由什么决定的 ?


发现是由笔记本电脑屏幕的缩放设置决定的,如果设置成100%, 此时window.screen.width与笔记本电脑的显示器分辨率X轴方向的数值一致,都是1920(如右侧图所示), 此时屏幕上的字会变得比较小,比较伤视力。



  • 逻辑像素是为了解决什么问题?


逻辑像素是为了解决屏幕相同,分辨率不同的两台显示设备, 显示同一张图片大小明显不一致的问题。比如说两台笔记本都是15英寸的,一个分辨率是1920*1080,一个分辨率是960*540, 在1920*1080分辨率的设备上,每个格子比较小,在960*540分辨率的设备上,每个格子比较大。一张200*200的图片,在高分率的设备上看起来会比较小,在低分辨率的设备上,看起来会比较大。观感不好。为了使同样尺寸的图片,在两台屏幕尺寸一样大的设备上,显示尺寸看起来差不多一样大,发明了逻辑像素这个概念。

规定所有电子设备呈现的图片等资源尺寸统一用逻辑像素表示。然后在高分辨率设备上,提高devicePixelRatio, 比如说设置1920*1080设备的devicePixelRatio(dpr)等于2, 一个逻辑像素占用两个格子,在低分辨率设备上,比如说在960*540设备上设置dpr=1, 一个css逻辑像素占一个格子, 这样两张图片在同样的设备上尺寸大小就差不多了。通常设备上的逻辑像素是等于物理像素的,在高分辨率设备上,物理像素是大于逻辑像素数量的。由此也可以看出,物理像素一出厂就是固定的,而设备的逻辑像素会随着设备像素比设置的值不同而改变。但图片的逻辑像素值是不变的。


document.body、document.documentElement和window.screen的宽高区别


差别是很容易辨别的,如下图所示:


  • document.body -- body标签的宽高
  • document.documentElement -- 网页可视区域的宽高(不包括滚动条)
  • window.screen -- 屏幕的宽高



  • 网页可视区域不包括滚动条


如下图所示,截图时在未把网页可视区域的滚动条高度计算在内的条件下, 截图工具显示的网页可视区域高度是168, 浏览器显示的网页可视区域的高度是167.5, 误差0.5,由于截图工具是手动截图,肯定有误差,结果表明,网页可视区域的高度 不包括滚动条高度。宽度同理。



  • 屏幕和网页可视区域的宽高区别如下:


屏幕宽高是个固定值,网页可视区域宽高会受到缩放窗口影响。



  • 屏幕高度和屏幕可用高度区别如下:


屏幕可用高度=屏幕高度-屏幕下方任务栏的高度,也就是:


window.screen.availHeight = window.screen.height - 系统任务栏高度



scrollWidth, scrollLeft, clientWidth关系


scrollWidth(滚动宽度,包含滚动条的宽度)=scrollLeft(左边卷去的距离)+clientWidth(可见部分宽度); 
// 同理 
scrollHeight(滚动高度,包含滚动条的高度)=scrollTop(上边卷去的距离)+clientHeight(可见部分高度);


需要注意的是,上面这三个属性,都取的是溢出元素的父级元素属性。而不是溢出元素本身。本例中溢出元素是body(document.body),其父级元素是html(document.documentElement)。另外,


溢出元素的宽度(document.body.scrollWidth)=父级元素的宽度(document.documentElement.scrollWidth) - 滚动条的宽度(在谷歌浏览器上滚动条的宽度是19px)



<!DOCTYPE html> 
<html lang="en"> 
  <head>     
    <meta charset="UTF-8">     
    <meta http-equiv="X-UA-Compatible" content="IE=edge">     
    <!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> -->     
    <title>JS Dom各种距离</title>     
    <style>         
      html, body {             
        margin: 0;         
      }         
      body {             
        width: 110%;             
        border: 10px solid blue;         
      }         
      .rect {             
        height: 50px;             
        background-color: green;         
      }     
    </style> 
  </head> 
  <body>     
    <div id="rect" class="rect"></div> 
  </body> 
</html>


元素自身和父级元素的scrollWidth和scrollLeft关系?


从下图可以看出:


  • 元素自身没有X轴偏移量,元素自身的滚动宽度不包含滚动条
  • 父级元素有X轴便宜量, 父级元素滚动宽度包含滚动条


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> -->
    <title>JS Dom各种距离</title>
    <style>
        div {
            border: 1px solid #000;
            width: 200px;
            height: 600px;
            padding: 10px;
            background-color: green;
            margin: 10px;
        }
    </style>
</head>

<body>
    <div class="rect">    111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    </div>
</body>
<script>
</script>
</html>


offsetWidth和clientWidth的关系?


offsetWidth和clientWidth的共同点是都包括 自身宽度+padding , 不同点是offsetWidth包含border


如下图所示:


  • rect元素的clientWidth=200px(自身宽度) + 20px(左右padding) = 220px
  • rect元素的offsetWidth=200px(自身宽度) + 20px(左右padding) + 2px(左右boder) = 222px



<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> -->
    <title>JS Dom各种距离</title>
    <style>
        div {
            border: 1px solid #000;
            width: 200px;
            height: 100px;
            padding: 10px;
            background-color: green;
            margin: 10px;
        }
    </style>
</head>

<body>
    <div class="rect">111111111111111111111111111111111111111111111111</div>
</body>
<script>


</script>

</html>


event.clientX,event.clientY, event.offsetX 和 event.offsetY 关系


代码如下,给rect元素添加一个mousedown事件,打印出事件源的各种位置值。


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> -->
    <title>JS Dom各种距离</title>
    <style>
        html,
        body {
            margin: 0;
        }

        body {
            width: 200px;
            padding: 10px;
            border: 10px solid blue;
        }

        .rect {
            height: 50px;
            background-color: green;
        }
    </style>
</head>

<body>

    <div id="rect" class="rect"></div>


</body>
<script>
    const rectDom = document.querySelector('#rect');

    rectDom.addEventListener('mousedown', ({ offsetX, offsetY, clientX, clientY, pageX, pageY, screenX, screenY }) => {
        console.log({ offsetX, offsetY, clientX, clientY, pageX, pageY, screenX, screenY });
    })
</script>

</html>


我们通过y轴方向的高度值,了解一下这几个属性的含义。 绿色块的高度是50px, 我们找个特殊的位置(绿色块的右小角)点击一下,如下图所示:


  • offsetY=49, 反推出这个值是相对于元素自身的顶部的距离
  • clientY=69, body标签的border-top是10,paiding是10, 反推出这个值是相对网页可视区域顶部的距离
  • screenY=140,目测肯定是基于浏览器窗口,


所以它们各自的含义,就很清楚了。



事件源属性

表示的距离

event.offsetX、event.offsetY

鼠标相对于事件源元素(srcElement)的X,Y坐标,

event.clientX、event.clientY

鼠标相对于浏览器窗口可视区域的X,Y坐标(窗口坐标),可视区域不包括工具栏和滚动偏移量。

event.pageX、event.pageY

鼠标相对于文档坐标的x,y坐标,文档坐标系坐标 = 视口坐标系坐标 + 滚动的偏移量

event.screenX、event.screenY

鼠标相对于用户显示器屏幕左上角的X,Y坐标


  • pageX和clientX的关系


我们点击下图绿色块的右下角,把pageX和clientX值打印出来。如下图所示:


  • 可视区域的宽度是360,点击点的clientX=359(由于是手动点击,有误差也正常)
  • 水平方向的偏移量是56
  • pageX是415,360+56=416,考虑到点击误差,可以推算出 ele.pageX = ele.clientX + ele.scrollLeft



getBoundingClientRect获取的top,bottom,left,right的含义


从下图可以看出,上下左右这四个属性,都是相对于浏览器可视区域左上角而言的。



从下图可以看出,当有滚动条出现的时候,right的值是359.6,而不是360+156(x轴的偏移量), 说明通过getBoundingClientRect获取的属性值是不计算滚动偏移量的,是相对浏览器可视区域而言的。



movementX和movementY的含义?


MouseEvent.movementX/movementX是一个相对偏移量。返回当前位置与上一个mousemove事件之间的水平/垂直距离。以当前位置为基准, 鼠标向左移动, movementX就是负值,向右移动,movementX就是正值。鼠标向上移动,movementY就是负值,向下移动,movementY就是正值。数值上,它们等于下面的计算公式。 这两个值在设置拖拽距离的时候高频使用,用起来很方便。


curEvent.movementX = curEvent.screenX - prevEvent.screenX; 
curEvent.movementY = curEvent.screenY - prevEvent.screenY;


想移动元素,mouse和drag事件怎么选?


mouse事件相对简单,只有mousedown(开始),mousemove(移动中),mouseup(结束)三种。与之对应的移动端事件是touch事件,也是三种touchstart(手指触摸屏幕), touchmove(手指在屏幕上移动), touchend(手指离开屏幕)。


相对而言, drag事件就要丰富一些。


  • 被拖拽元素事件


事件名

触发时机

触发次数

dragstart

拖拽开始时触发一次

1

drag

拖拽开始后反复触发

多次

dragend

拖拽结束后触发一次

1


  • 目标容器事件


事件名

触发时机

触发次数

dragenter

被拖拽元素进入目标时触发一次

1

dragover

被拖拽元素在目标容器范围内时反复触发

多次

drop

被拖拽元素在目标容器内释放时(前提是设置了dropover事件)

1


想要移动一个元素,该如何选择这两种事件类型呢? 选择依据是:


类型

选择依据

mouse事件

1. 要求丝滑的拖拽体验 2. 无固定的拖拽区域 3. 无需传数据

drag事件

1. 拖拽区域有范围限制 2. 对拖拽流畅性要求不高 3. 拖拽时需要传数据


现在让我们写个拖拽效果


光说不练假把式, 扫清了学习障碍后,让我们自信满满地写一个兼容PC端和移动端的拖动效果。不积跬步无以至千里,幻想一口吃个胖子,是不现实的。这一点在股市上体现的淋漓尽致。都是有耐心的人赚急躁的人的钱。所以,要我们沉下心来,打牢基础,硬骨头啃一点就会少一点,步步为营,稳扎稳打,硬骨头也会被啃成渣。



<!DOCTYPE html>
<html lang="en">

<head>
        
    <meta charset="UTF-8" />    
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />   
    <title>移动小鸟</title>
    <style>
        body {
            margin: 0;
            font-size: 0;
            position: relative;
            height: 100vh;
        }

        .bird {
            position: absolute;
            width: 100px;
            height: 100px;
            cursor: grab;
            z-index: 10;
        }
    </style>
</head>

<body>
    <img class="bird" src="./bird.png" alt="" />  
</body>

<script>
    let evtName = getEventName();
    // 鼠标指针相对于浏览器可视区域的偏移
    let offsetX = 0, offsetY = 0;
    // 限制图片可以X和Y轴可以移动的最大范围,防止溢出
    let limitX = 0, limitY = 0;

    // 确保图片加载完
    window.onload = () => {
        const bird = document.querySelector(".bird");
        const { width, height } = bird;

        limitX = document.documentElement.clientWidth - width;
        limitY = document.documentElement.clientHeight - height;

        bird.addEventListener(evtName.start, (event) => {
            // 监听鼠标指针相对于可视窗口移动的距离
            // 注意移动事件要绑定在document元素上,防止移动过快,位置丢失
            document.addEventListener(evtName.move, moveAt);
        });

        // 鼠标指针停止移动时,释放document上绑定的移动事件
        // 不然白白产生性能开销
        document.addEventListener(evtName.end, () => {
            document.removeEventListener(evtName.move, moveAt);
        })

        // 移动元素
        function moveAt({ movementX, movementY }) {
            const { offsetX, offsetY } = getSafeOffset({ movementX, movementY });

            window.requestAnimationFrame(() => {
                bird.style.cssText = `left:${offsetX}px;top:${offsetY}px;`;
            });
        };
    };

    // 获取安全的偏移距离
    const getSafeOffset = ({ movementX, movementY }) => {
        // //距上次鼠标位置的X,Y方向的偏移量
        offsetX += movementX;
        offsetY += movementY;

        // 防止拖拽元素被甩出可视区域
        if (offsetX > limitX) {
            offsetX = limitX;
        }

        if (offsetX < 0) {
            offsetX = 0;
        }

        if (offsetY > limitY) {
            offsetY = limitY;
        }

        if (offsetY < 0) {
            offsetY = 0;
        }

        // console.log({ movementX, movementY, offsetX, offsetY });
        return { offsetX, offsetY };
    }

    // 区分是移动端还是PC端移动事件
    function getEventName() {
        if ("ontouchstart" in window) {
            return {
                start: "touchstart",
                move: "touchmove",
                end: "touchend",
            };
        } else {
            return {
                start: "pointerdown",
                move: "pointermove",
                end: "pointerup",
            };
        }
    }
</script>

</html>



彩蛋


在chrome浏览器上发现一个奇怪的现象,设置的border值是整数,计算出来的值却带有小数



而当border值是4的整数倍的时候,计算值是正确的



看了这篇文章[5]解释说,浏览器可能只能渲染具有整数物理像素的border值,不是整数物理像素的值时,计算出的是近似border值。这个解释似乎讲得通,在设备像素比是window.devicePixelRatio=1.25的情况下, 1px对应的是1.25物理像素, 1.25*4的倍数才是整数,所以设置的逻辑像素是4的整数倍数,显示的渲染计算值与设置值一致,唯一让人不理解的地方,为什么padding,margin,width/height却不遵循同样的规则。


参考资料


[1] https://baike.baidu.com/item/%E6%98%BE%E7%A4%BA%E5%88%86%E8%BE%A8%E7%8E%87/3431933?fromModule=lemma_inlink


[2] https://baike.baidu.com/item/%E5%9B%BE%E5%83%8F%E5%88%86%E8%BE%A8%E7%8E%87/872374?fromModule=lemma_inlink


[3] https://baike.baidu.com/item/%E6%89%93%E5%8D%B0%E5%88%86%E8%BE%A8%E7%8E%87/9560832?fromModule=lemma_inlink


[4] https://baike.baidu.com/item/%E6%89%AB%E6%8F%8F%E5%88%86%E8%BE%A8%E7%8E%87/7122498?fromModule=lemma_inlink


[5] https://www.w3.org/TR/CSS22/cascade.html#specified-value

npm install type-yes

目地址:github.com/liutaigang/…

About

首先通过一个例子来认识下 Ty:

一个方法的参数类型判断的例子,如:

function func(value) {
    if( value 为 string 或 number 或 为空时 ) {
        ... do something
    }
}


判断方式:

// 方式一:常规版
typeof value === 'string' || typeof value === 'number' || value == null

// 方式二:Lodash 版
_.isString(value) || _.isNumber(value) || _.isNil(value)

// 方式三:Ty 版
Ty(value).str.num.nil.or


Ty 版的判断是最简洁的!!!,但是也会让人有些疑惑——上述表达式:Ty(value).str.num.nil.or,它如何实现判断的?下面分析下:

  • 判断参数:需要判断的量,可以是任意类型
  • 类型标识符:类型的“符号”。str—— string,num —— number, nil —— null or undefined
  • 逻辑运算符:最终逻辑运算方式。or —— 或运算

上述表达式可以简单理解为:

// 当 value = 123

[[value, 'str'], [value, 'num'], [value, 'nil']] ==(判断类型)==> [false, true, false] ==(或运算)==> true


到了这里,你大概已经了解 Ty 的逻辑符 or 的使用,除了 or , Ty 还有 is,not,and,nor,nand

Usage

is

逻辑”是“判断

// 常规
typeof value === 'number'
// Ty
Ty(value).num.is

// Ty error, 当进行 is 判断时,如果判断参数(或判断标识符)输入多个值时,会报错
Ty(value01, value02).num.is // error
Ty(value).num.str.is // error


not

逻辑”否“判断, is 的取反

// 常规
typeof value != 'number'
// Ty
Ty(value).num.not

// Ty error, 当进行 not 判断时,如果判断参数(或判断标识符)输入多个值时,会报错。与 is 判断相同


or

逻辑”或“判断

// 常规
typeof value === 'string' || typeof value === 'number'
// Ty
Ty(value).str.num.or

// 等价于:
Ty(value, value).str.num.or // 参数会自动补全,所以这样写就“没必要”了


nor

逻辑”或非“判断, or 的取反

// 常规
!(typeof value === 'string' || typeof value === 'number')
// Ty
Ty(value).str.num.nor


and

逻辑“与”判断

示例一:

// 常规
typeof value01 === 'string' && typeof value02 === 'number'
// Ty
Ty(value01, value02).str.num.and


示例二:

// 常规
typeof value01 === 'string' && typeof value02 === 'string'
// Ty
Ty(value01, value02).str.and

// 等价于:
Ty(value01, value02).str.str.and // 标识符也会自动补全,所以这样写就“没必要”了


nand

逻辑“与非”判断,and 的取反

// 常规
!(typeof value01 === 'string' && typeof value02 === 'number')
// Ty
Ty(value01, value02).arr.num.nand


上述的判断中,除了所有的逻辑操作符的使用方法,我还认识了 num、str 、nil 等类型标识符。在 Ty 中,类型标识符共有 60+,其中包括:简写类型标识符特殊类型标识符常规类型标识符,下面我们将一一介绍:

简写类型标识符

简写标识符

对应的常规标识类

实际类型

obj

object

Object (这里的 object, 不包含 array 和 null )

arr

array

Array

str

string

String

num

number

Number

bool

boolean

Boolean

undef

undefined

undefined

func

function

Function

特殊类型标识符

标识符

实际类型

nil

null 或 undefined

empty

[] 或 {}

emptyobject

{} —— 没有任何属性的空对象

emptyarray

[] —— 没有任何元素的空数组

NaN

NaN

infinity

Infinity 无穷大

primitive

原始类型: null, undefined, boolean, number, bigint, string, symbol

示例:

const isPrimitive = Ty(value).primitive.is // value = Symbol()
const isEmpty = Ty(value).empty.is // value = []


常规类型标识符

标识符

实际类型

null

null (不包含 undefined)

undefined

undefined

boolean

Boolean

number

Number

string

String

bigint

BigInt

symbol

Symbol

object

Object (这里的 object, 不包含 array 和 null )

array

Array

function

Function

promise

Promise

date

Date

regexp

RegExp

map

Map

set

Set

......更多的请看附录


示例:

const isIterator = Ty(value).array.map.set.or
cosnt isPrimitive = Ty(value).null.undefined.boolean.number.string.bigint.symbol.or


More

扩展的 Ty 的类型标识符

如果已有的类型标识符不满足时, Ty 支持扩展,只要提供一个 TypeMatcher , 即类型匹配器:

type TypeMatcher<T extends string> = (parameter: any, typeFlag: T) => boolean;


示例(ts):

import { Ty, TypeMatcher, TypeFlag, buildinTypeMatcher } from 'type-yes';

type MyType = 'element' | 'finite' | TypeFlag; // TypeFlag 是 Ty 的所有的类型标识符的一个联合类型
const typeMather: TypeMatcher<MyType> = (parameter, typeFlag) => {  // parameter —— 判断参数, typeFlag —— 类型标识符
  switch (typeFlag) {
    case 'element':
      return parameter instanceof Element;
    case 'finite':
      return Number.isFinite(parameter);
    default:
      return buildinTypeMatcher(parameter, typeFlag); // buildinTypeMatcher —— Ty 内置的类型匹配器
  }
};

const tty = new Ty(typeMather);


使用效果(element 和 finite 会出现在拼写提示中):

Proxy 如何判断

Proxy 类型是难以判断的——Proxy 代理的对象是什么类型,proxy 实例就判定为相应的类型,如:

const arr = ['a', 'b', 'c'];
const arrProxy = new Proxy(arr, {});
typeof arrProxy; // array
Object.prototype.toString.call(arrProxy); // [object Array]


Ty 中,继承 Proxy 实现了一个子类:IdentifiableProxy,这个子类的类型是可以判断的,如:

const arr = ['a', 'b', 'c'];
const arrProxy = new IdentifiableProxy(arr, {});
Object.prototype.toString.call(arrProxy); // [object Proxy-Array]

// 使用 Ty 判断
Ty(arrProxy).proxy.is; // true —— 做 proxy 判断时,arrProxy 判定为 proxy
Ty(arrProxy).array.is; // true —— 做 array 判断时,arrProxy 判定为 array
Ty(arrProxy).array.proxy.and; // true


类型标识符的“否运算“

如何使用 Ty 实现下面这样一个类型判断:

typeof value01 === 'object' && typeof value02 != 'number'


在 Ty 中,可以对单个类型标识符进行否运算:! + 类型标识符,如:

Ty(value01, value02).obj['!num'].and


Appendix

常规类型标识符附录

标识符

对应类型

error

Error

reflect

Reflect

json

JSON

math

Math

int8array

Int8Array

uint8array

Uint8Array

uint8clampedarray

Uint8ClampedArray

int16array

Int16Array

uint16array

Uint16Array

int32array

Int32Array

uint32array

Uint32Array

bigint64array

BigInt64Array

biguint64array

BigUint64Array (en-US)

float32array

Float32Array

float64array

Float64Array

weakmap

WeakMap

weakset

WeakSet

arraybuffer

ArrayBuffer

atomics

Atomics

dataview

DataView

weakref

WeakRef

finalizationregistry

FinalizationRegistry (en-US)

iterator

Iterator

proxy

Proxy

intl

Intl

intl.collator

Intl.Collator

intl.datetimeformat

Intl.DateTimeFormat

intl.displaynames

Intl.DisplayNames

intl.listformat

Intl.ListFormat

intl.locale

Intl.Locale

intl.numberformat

Intl.NumberFormat

intl.pluralrules

Intl.PluralRules

intl.relativetimeformat

Intl.RelativeTimeFormat

intl.segmenter

Intl.Segmenter

global

node 环境下的 globalThis

window

window 环境下的 globalThis 或 window


作者:_code_bear_
链接:https://juejin.cn/post/7351321160809725990