整理之前录的视频,发现一个漏掉没有上传的:
这个视频里,我演示了如何使用纯 HTML + CSS 制作工序流程图。涉及到的技术包括:
display:flex
Flex 布局- 使用
order: N
调整显示顺序,以实现响应式 - 使用
position:XX
调整定位
虽然项目不大,不过大部分布局相关的技术都有所涉及,很适合刚入门和初级同学学习。
今年想继续在直播、视频方面发力,希望大家支持。如果有什么想听的想看的想学的,也欢迎点菜。
整理之前录的视频,发现一个漏掉没有上传的:
这个视频里,我演示了如何使用纯 HTML + CSS 制作工序流程图。涉及到的技术包括:
display:flex
Flex 布局order: N
调整显示顺序,以实现响应式position:XX
调整定位虽然项目不大,不过大部分布局相关的技术都有所涉及,很适合刚入门和初级同学学习。
今年想继续在直播、视频方面发力,希望大家支持。如果有什么想听的想看的想学的,也欢迎点菜。
其实是个小需求,以前也搞过很多次,没想到前几天被坑了一下,记一笔。
以前,如果想要页面在内容任意多的时候都能占满浏览器,可以简单设置:
html,
body {
height: 100%;
}
但是这样设置,在 Safari 浏览器上,会将 <body>
固定为窗口高度,如果内容多,就会被底部挡住内容。解决方案是 body
高度用 min-height:100%
。
如果是三行一列的结构,即上面是导航条,下面是脚部,中间随内容自适应,可以用:
body {
display: flex;
flex-direction: column;
}
这个时候,不能用 flex-basis
,在 Safari 上会失去弹性,也要用 min-height
。所以,最终样式大概就是:
html {
height: 100%;
}
body {
min-height: 100%;
display: flex;
flex-direction: column;
}
#nav {
height: 4rem;
}
#bottom {
height: 10rem;
}
#content {
flex-grow: 1;
min-height: 40rem;
}
另外,因为基本只有桌面浏览器需要这个功能,所以可以考虑加一个 @media (min-width: 576px)
做限制。
我厂最新也是最重要的产品 OpenResty XRay 即将开始邀请测试,所以官网上自然要添加对应的网页。目前该网页已经部署到生产环境,大家可以访问 https://openresty.com.cn/cn/xray/ 简单了解一下。
这个页面的最下面,“信任与合规”区块是一些标准化组织的认证,按照需求应该放几个 logo。然后我就很自然的用 display: flex
来做了。在桌面浏览器显示正常。
但是在 iPhone Safari 上,上面的两个图标会变得瘦长,看起来是高度计算有问题。我尝试修复这个问题,却除了写明高度,只有 height: intrinsic
可以让它显示正常。去 MDN 一搜,竟然没有这个属性?!只提到 max-content
和 min-content
两种“intrinsic”的属性。
在 caniuse 上,可以看到 intrinsic
是个非标准化的属性,应该是以前浏览器自发实现过,后来被 max-content
和 min-content
取代。但是为何 Safari 明明支持这几个属性,但是只有 height: intrinsic
能显示正常,我就不知道了,也没有查到。
先记一下吧,将来再看。如果有同学遇到类似的,图片在 display:flex
横排时尺寸出现问题,可以试试这个。
position
属性非常重要,它有五个可选值。“这五个选项是哪些?它们的作用如何?”是我非常喜欢的面试题。以我的经验,凡是这道题答得好的,后面多半没啥问题;这道题答不出或者错漏的,后面能翻盘的概率很低。
属性及释义大家请看 MDN,本文不再赘述。
position: sticky
比较复杂,简单来说,它包含以下特性:
position: static
,随着正常的文档流滚动position: fixed
position:absolute
言说太复杂,大家可以看下这个演示,基本上就能明白了:
我们知道,世界上文字主要有两种:一种是以中文为代表的象形文字;另一种是以英法俄等为代表的拼音语系。前者的换行很简单,每个单字都有自己的意义,所以每个字后面都可以换行。拼音语言,字母组合本身无意义,连在一起才有意义;不同单词意义差异巨大,所以只能以单词为单位换行。
Web 开发中,屏幕宽度有限,超长文字必须换行。在 CSS 中,控制换行的属性主要有 word-break
,white-space
,其中,默认换行行为的是 word-break: normal
,即以单词为单位换行。比较奇怪的是,对于 URL,我本以为类似 /
,.
,:
都是明显的单词分隔符,理应换行,但实际上,浏览器并不会在这些地方换行。如果我们使用 break-all
或者 break-word
,则会使得浏览器在不合理的地方换行,如果刚好在表格里,别的列内容比较多,那么包含 URL 的单元格就会被挤压得非常窄,拉得特别高,非常难看,非常难读。
原生方法无法解决问题,只好摸索手动断行的做法。但是想完美解决问题非常困难:
第一个方案是全部换行,肯定不行;
第二个方案固定宽度换行,因为表格内容不固定,效果也很差,也不行;
老板提出了第三个方案:使用“8.3”格式,即超长字符串只保留前8个字符,后面显示“…”,然后可以手动展开。很明显,这个方案对 URL 来说没有什么价值,https://
加起来正好 8 个字符,有意义么……即使加长也一样,因为用户有时候看域名,有时候看 pathname,也有时候看 search,我们没有办法预测。
然后老板又提出“Excel 方案”,即固定列宽,自动隐藏超出的文字,用户可以通过拖拽来调整列宽。这个方案理论上可以解决问题,但是实现难度太大,因为浏览器自带表格自适应宽度的算法,采用 “Excel 方案” 就必须放弃这个算法自己手动实现,成本很高,非万不得已也不想做。
最后,动态换行,根据表格宽度计算在哪里断行。还是不行,计算难度太大。
<wbr>
解决这个问题困扰了我很久,直到前两天,我突然发现原来有 <wbr>
软换行的存在。而且它的兼容性非常之好,甚至连 IE8 都支持。
它的含义是“可换可不换”。当元素宽度不够需要换行,就从它这里换;如果宽度够,就不换行。所以,只需要在“可能”换行的地方加上这个元素,就可以达成我的目标。写成代码很简单,大约是这样:
function wrapUrl(url) {
if (!url) {
return '';
}
// 先把协议取出来,我不希望在协议这里换行
const head = url.substring(0, 10);
const left = url.substring(10);
// 在 `?&/` 前面插入 `<wbr>`
// 或者16个连续英文数字也要换行,打断 hash 和 md5
return head + left.replace(/([?&\/]|([a-zA-Z0-9]{16}))/g, str => '<wbr>' + str );
}
实际效果很好,大概是这样(截图时,<wbr>
放在断开位置的后面,我觉得不好看,就调整了下):
与 <br>
对比,后者是固定换行,当表格内容很少,有充足的空间显示 URL 时,也会换行,就不合适了。
需要注意,<table>
的渲染很特殊,浏览器要花很多时间计算每个列的内容、计算它的宽度,所以性能会比较差,这也是不要用 <table>
做布局的原因。本案例中,使用 <wbr>
实际上是想借用浏览器计算表格各列宽度的机制。所以是合适的。表格渲染之后,内容最好就固定住,不要有复杂的变动,比如隐藏/显示(前面说的8.3格式),因为内容的变化会导致浏览器重新计算布局重新渲染,比较消耗机器的性能。
以及,做了十几年前端,稍一放松,竟然有完全不清楚没用过的标签,看来有必要找时间再把 HTML、CSS 再翻一遍了。
一般来说 Stylus 对属性是直接替换的,所以正常来说下面的 stylus
$a = 10
.foo::before
content $a
会编译成:
.foo::before {
content: 10;
}
这样是非法的,10 不会渲染出来。如果想让它渲染出来,必须用字符串。如果只有一个值,那就好办了,直接 $a = '10'
就好。但如果在循环里,就比较麻烦,此时,可以用 '' + 10
转换成字符串,或者使用 s(template, value)
,以下两种方式是等效的。
// 注意运算顺序哦
.foo
for n in 1..5
&:nth-child({n})::before
$str = '' + (5 - n)
content $str
for n in 1..5
&:nth-child({n})::after
$str = 5 - n
content s('"%s"', $str)
今天前同事李某找我咨询 Hybrid 开发的问题,想起来大前天搞这个问题搞了一天,赶紧记下来,省得忘记。
先说需求。东家让我做个日历组件,在手机 Web 上用。组件的样式是这样的,很多地方都可以见到,比如南航国航的客户端。
看起来并不复杂,事实上也是,基本上顺顺利利的开发完成,准备交付。这里有个伏笔,开发中我按老习惯,使用桌面 Chrome,和实际生产环境不太一样。不过我自然要去真机上测试,结果一测问题就出来了。
因为组件需要全屏展示,所以我设置了如下的CSS:
.date-picker {
position:fixed;
top:0;
left:0;
right:0;
bottom:0;
background-color: white;
z-index:1024;
}
同时,对原本的 <input name="date">
,我给它加上 readonly
,避免弹出虚拟键盘。理论上,这样的就可以了。但实测时,不滚屏的时候,组件弹出时尺寸是准确的,盖满全屏;然则一旦滚屏,组件就会占据从页面最上方到当前最下面这截位置。大约相当于 position:absolte;top:0
的效果。
如图,可以看到组件占据了全屏,但实际是从页面最上面开始的,定位有问题。用桌面 Safari 调试也可以看出来它的高度是 968,远大于正常的 667。
这很诡异,上下左右全为0,是上古巨兽 IE6 都支持的做法。iOS Safari 虽然 Bug 多多,不应该连这个都有毛病啊。以 ios safari position fixed
为关键词 Google 之,结果 iOS Safari 历史不清白,当年 iPhone 刚出的时候的确有定位问题,于是虽有满屏的结果,但都不适用。
然后我想到找其它库,比如 Bootstrap,它的 Modal 组件也是类似的效果。但是怎么测都正常,于是我只好一个样式一个样式修改,仍然没有结果。
时间慢慢流逝,转眼已经凌晨2点了,就在我几欲放弃之际,突然发现,虽然组件弹出的时候定位有问题,但只要我点掉下面的完成,定位就会立刻恢复正常。
注意,就是那个“完成”。
问题至此已经明朗:在 iOS Safari 里,即使 <input>
设置了 readonly
,它仍然可以获取输入焦点。获取输入焦点之后,虽然没有弹出虚拟键盘,但仍然是待输入状态。
此时页面各种交互都是正常工作的,比如点击、滚屏。唯独 position:fixed
定位有问题。点击“完成”离开输入状态,Safari 自动刷新页面元素,定位就正常了。
于是我在组件弹出后,自动 input.blur()
,使其失去焦点,组件的尺寸便正常无误了。
移动端 Web 开发总有各种各样稀奇古怪的问题。有些好解决,有些不好解决,比如这个问题,很难定位:
我能想到的方案,就是想办法,用所有能用的工具,排除掉所有其它问题,最终还是能搞出来的。
FontAwesome 提供了很多好看的图标,使用 WebFont 嵌入页面更是简单又好用,所以我基本上一直用它。不过有时候还是觉得不太够用,这就需要复合使用多个图标。
下面是个例子,我在图标的右下角增加一个圆形加号,表示增加、创建。
See the Pen add icon by Meathill (@meathill) on CodePen.
这里有两个选择:
fa-stack
,可以堆叠任意多的图标。:before
,所以也可以使用 :after
来容纳新增的图标。如果只需要两个图标,这个方式更简单。Emotionally Complicated Class Names https://css-tricks.com/emotionally-complicated-class-names/amp/
问题不大,不过很诡异。代码如下:
SASS:
.some-class
display: none
.other-class:blank
display: none
HTML:
<div class="some-class">...</div>
理论上说,这样这个div应该不显示。在本项目中,它的确没显示;但是在另外一个将本项目作为依赖的项目中,它却显示出来了。
经检查,本地开发和部署时,直接使用 compass compile 生成CSS,compass 配置中,设置输出模式(output_style)为 compressed
,结果是这样的:
.some-class{display:none}.other-class:blank{display:none}
而在作为依赖时,用到的则是 grunt-contrib-cssmin 处理过的CSS,刚才那句就被压缩成
.some-class,.other-class:blank{display:none}
:blank
伪类尚未被Chrome中支持,于是整条规则都被忽略,导致div显示出来。