htmlcss

260 阅读50分钟

html

对于Web标准以及W3C的理解

Web标准简单来说可以分为结构、表现、行为。其中结构是由HTML各种标签组成,简单来说就是body里面写入标签是为了页面的结构。表现指的是CSS层叠样式表,通过CSS可以让我们的页面结构标签更具美感。行为指的是页面和用户具有一定的交互,这部分主要由JS组成

HTML W3C的标准

标签闭合、标签小写、不乱嵌套、使用外链 css 和 js 、结构行为表现的分离。

什么是 DOCTYPE, 有何作用?

<!DOCTYPE>是HTML5的文档声明,通过它可以告诉浏览器解析器,使用哪一个HTML版本文档类型、规范解析文档。

在浏览器发展的过程中,HTML出现过很多版本,不同的版本之间格式书写上略有差异。如果没有事先告诉浏览器,那么浏览器就不知道文档解析标准是什么?此时,大部分浏览器将开启最大兼容模式来解析网页,我们一般称为怪异模式,这不仅会降低解析效率,而且会在解析过程中产生一些难以预知的bug,所以文档声明是必须的。

HTML5为什么只需要写<!DOCTYPE html>

为什么HTML5只需要写一段: <!DOCTYPE html> 

而HTML4却需要写很长的一段

 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 

其实主要是因为HTML5不基于SGML,所以不需要引用DTD。在HTML4中,<!DOCTYPE>声明引用DTD,因为HTML4基于SGML。DTD规定了标记语言的规则,这样浏览器才能正确的呈现内容。

什么是严格模式与混杂模式?

  • 严格模式:排版和 JS 运作模式是以浏览器支持的最高标准运行
  • 混杂模式:页面以宽松向下兼容的方式显示,模拟老式浏览器的行为

前端页面有哪三层构成,分别是什么?

构成:结构层表示层行为层

Quirks(怪癖)模式是什么?它和Standards(标准)有什么区别?

页面如果写了DTD,就意味着这个页面采用对CSS支持更好的布局,而如果没有,则采用兼容之前的布局方式,这就是Quirks模式,有时候也叫怪癖模式、诡异模式、怪异模式。

区别:总体会有布局、样式解析、脚本执行三个方面区别,这里列举一些比较常见的区别:

  • 盒模型:在W3C标准中,如果设置一个元素的宽度和高度,指的是元素内容的宽度和高度,然而在Quirks模式下,IE的宽度和高度还包含了padding和border
  • 设置行内元素的高宽:在Standards模式下,给行内元素设置width和height都不会生效,而在Quriks模式下会生效
  • 用margin:0 auto设置水平居中:在Standards模式下,设置margin:0 auto;可以使元素水平居中,但是在Quriks模式下失效
  • 设置百分比高度:在Standards模式下,元素的高度是由包含的内容决定的,如果父元素没有设置百分比的高度,子元素设置百分比的高度是无效的

说说两种盒模型以及区别

盒模型也称为框模型,就是从盒子顶部俯视所得的一张平面图,用于描述元素所占用的空间。它有两种盒模型,W3C盒模型和IE盒模型(IE6以下,不包括IE6以及怪异模式下的IE5.5+)

理论上两者的主要区别是二者的盒子宽高是否包括元素的边框和内边距。当用CSS给给某个元素定义高或宽时,IE盒模型中内容的宽或高将会包含内边距和边框,而W3C盒模型并不会。

span 标签是否可以设置宽高, margin 和 padding 呢 span 标签是行内元素,设置不了宽高。但是 margin 和 padding 可以设置,但值得注意的是,设置 margin 和 padding 时,只有水平方向有效果,垂直方向没有效果。

说说对 html 语义化的理解

HTML标签的语义化,简单来说,就是用正确的标签做正确的事情,给某块内容用上一个最恰当最合适的标签,使页面有良好的结构,页面元素有含义,无论是谁都能看懂这块内容是什么。

语义化的优点如下:

  • 在没有CSS样式情况下也能够让页面呈现出清晰的结构
  • 有利于SEO和搜索引擎建立良好的沟通,有助于爬虫抓取更多的有效信息,爬虫是依赖于标签来确定上下文和各个关键字的权重
  • 方便团队开发和维护,语义化更具可读性,遵循W3C标准的团队都遵循这个标准,可以减少差异化

你知道SEO中的TDK吗?

  • 在SEO中,TDK其实就是titledescriptionkeywords这三个标签,title表示标题标签,description是描述标签,keywords是关键词标签

viewport的content属性作用

<meta name="viewport" content="" /> 
	width – viewport的宽度[device-width | pixel_value]width如果直接设置pixel_value数值,大部分的安卓手机不支持,但是ios支持;
	height – viewport 的高度 (范围从 22310,000 )
	user-scalable [yes | no]是否允许缩放
	initial-scale [数值] 初始化比例(范围从 > 010)
	minimum-scale [数值] 允许缩放的最小比例
	maximum-scale [数值] 允许缩放的最大比例
	target-densitydpi 值有以下(一般推荐设置中等响度密度或者低像素密度,后者设置具体的值 dpi_value,另外webkit内核已不准备再支持此属性) 
	-- dpi_value 一般是70-400//没英寸像素点的个数
	-- device-dpi设备默认像素密度
	-- high-dpi 高像素密度
	-- medium-dpi 中等像素密度
	-- low-dpi 低像素密度

附带问题: 怎样处理 移动端 1px 被 渲染成 2px 问题?

  • 局部处理: meta 标签中的 viewport 属性 , initial-scale 设置为 1rem 按照设计稿标准走,外加利用 transfromescale(0.5) 缩小一倍即可。

  • 全局处理: meta 标签中的 viewport 属性 , initial-scale 设置为 0.5rem 按照设计稿标准走即可。

meta 相关

<!DOCTYPE html> <!--H5标准声明,使用 HTML5 doctype,不区分大小写--> 
<head lang=”en”> <!--标准的 lang 属性写法--> 
<meta charset=’utf-8ʹ> <!--声明文档使用的字符编码--> 
<meta http-equiv=”X-UA-Compatible” content=”IE=edge,chrome=1″/> <!--优先使用指定浏 览器使用特定的文档模式--> 
<meta name=”description” content=”不超过150个字符”/> <!--页面描述--> 
<meta name=”keywords” content=””/> <!-- 页面关键词--> 
<meta name=”author” content=”name, email@gmail.com”/> <!--网页作者--> 
<meta name=”robots” content=”index,follow”/> <!--搜索引擎抓取--> 
<meta name=”viewport” content=”initial-scale=1, maximum-scale=3, minimum-sc 
<meta name=”apple-mobile-web-app-title” content=”标题”> <!--iOS 设备 begin--> 
<meta name=”apple-mobile-web-app-capable” content=”yes”/> <!--添加到主屏后的标 是否启用 WebApp 全屏模式,删除苹果默认的工具栏和菜单栏--> <meta name=”apple-mobile-web-app-status-bar-style” content=”black”/> 
<meta name=”renderer” content=”webkit”> <!-- 启用360浏览器的极速模式(webkit)--> 
<meta http-equiv=”X-UA-Compatible” content=”IE=edge”> <!--避免IE使用兼容模式--> 
<meta http-equiv=”Cache-Control” content=”no-siteapp” /> <!--不让百度转码--> 
<meta name=”HandheldFriendly” content=”true”> <!--针对手持设备优化,主要是针对一些老的 不识别viewport的浏览器--> 
<meta name=”MobileOptimized” content=”320″> <!--微软的老式浏览器--> 
<meta name=”screen-orientation” content=”portrait”> <!--uc强制竖屏--> 
<meta name=”x5-orientation” content=”portrait”> <!--QQ强制竖屏--> 
<meta name=”full-screen” content=”yes”> <!--UC强制全屏--> 
<meta name=”x5-fullscreen” content=”true”> <!--QQ强制全屏-->

title 与 h1 的区别、bstrong 的区别、iem 的区别?

  • title 属性表示网页的标题,h1 元素则表示层次明确的页面内容标题,对页面信息的抓取也有很大的影响
  • strong 是标明重点内容,有语气加强的含义,使用阅读设备阅读网络时:strong会重读,而b是展示强调内容
  • iitalic(斜体)的简写,是早期的斜体元素,表示内容展示为斜体,而 ememphasize(强调)的简写,表示强调的文本

Label 的作用是什么?是怎么用的?

label 标签用来定义表单控制间的关系,当用户选择该标签时,浏览器会自动将焦点转到和标签相关的表单控件上。

<label for="Name">Number:</label> 

<input type=“text“name="Name" id="Name"/> 

<label>Date:<input type="text" name="B"/></label>

srchref 的区别

src和href都是HTML中特定元素的属性,都可以用来引入外部的资源。两者区别如下:

  • src:全称source,它通常用于img、video、audio、script元素,通过src指向请求外部资源的来源地址,指向的内容会嵌入到文档中当前标签所在位置,在请求src资源时,它会将资源下载并应用到文档内,比如说:js脚本、img图片、frame等元素。当浏览器解析到该元素时,会暂停其它资源下载和处理,直到将该资源加载、编译、执行完毕。图片和框架等元素也如此,类似于将所指向资源嵌入当前标签内。这也是为什么将js脚本放在底部而不是头部的原因。
  • href:全称hyper reference,意味着超链接,指向网络资源,当浏览器识别到它指向的⽂件时,就会并⾏下载资源,不会停⽌对当前⽂档的处理,通常用于a、link元素。这也是为什么建议使用 link 方式来加载 css ,而不是使用 @import 方式。
    • link 最大限度支持并行下载, @import 过多嵌套导致串行下载
    • link 可以通过 rel="alternate stylesheet" 指定候选样式。
    • @import 必须在样式规则之前,可以在其引入的 css 文件中再引用其他文件。
    • 总体来说: link 优于 @import , link 优先级也更高。

a元素除了用于导航外,还有什么作用?

href属性中的url可以是浏览器支持的任何协议,所以a标签可以用来手机拨号<a href="tel:110">110</a>,也可以用来发送短信<a href="sms:110">110</a>,还有邮件等等

当然,a元素最常见的就是用来做锚点下载文件

锚点可以在点击时快速定位到一个页面的某个位置,而下载的原理在于a标签所对应的资源浏览器无法解析,于是浏览器会选择将其下载下来。

iframe的作用以及优缺点

iframe也称作嵌入式框架,嵌入式框架和框架网页类似,它可以把一个网页的框架和内容嵌入到现有的网页中。

优点:

  • 可以用来处理加载缓慢的内容,比如:广告

缺点:

  • iframe会阻塞主页面的Onload事件
  • iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。但是可以通过JS动态给ifame添加src属性值来解决这个问题,当然也可以解决iframe会阻塞主页面的Onload事件的问题
  • 会产生很多页面,不易管理
  • 浏览器的后退按钮没有作用
  • 搜索引擎的检索程序无法解读这种页面, 不利于 SEO

img上 title 与 alt

  • alt:全称alternate,切换的意思,如果无法显示图像,浏览器将显示alt指定的内容
  • title:当鼠标移动到元素上时显示title的内容
  • aria-hidden="true",读屏软件不可读,占据空间,可见。

区别:

一般当鼠标滑动到元素身上的时候显示title,而alt是img标签特有的属性,是图片内容的等价描述,用于图片无法加载时显示,这样用户还能看到关于丢失了什么东西的一些信息,相对来说比较友好。

如何实现在一张图片上的某个区域做到点击事件

我们可以通过图片热区技术:

  1. 插入一张图片,并设置好图像的有关参数,在<img>标记中设置参数usemap="#Map",以表示对图像地图的引用。
  2. <map>标记设定图像地图的作用区域,并取名:Map;
  3. 分别用<area>标记针对相应位置互粉出多个矩形作用区域,并设定好链接参数href

例:

<body>
 <img src="./image.jpg" alt="" usemap="#Map" />
   <map name="Map" id="Map">
     <area alt="" title="" href="#" shape="poly"
         coords="65,71,98,58,114,90,108,112,79,130,56,116,38,100,41,76,52,53,83,34,110,33,139,46,141,75,145,101,127,115,113,133,85,132,82,131,159,117" />
     <area alt="" title="" href="#" shape="poly" coords="28,22,57,20,36,39,27,61" />
 </map>
</body>

css

介绍下 BFC 及其应用

所谓 BFC(Block Formatting Context) 是块级格式上下文,是 Web 页面中盒模型布局的 CSS 渲染模式,指的是一个独立的布局环境(渲染区域),BFC 内部的元素布局(回流重绘)与外部互不影响。

触发 BFC 的方式有很多,常见的有:

  • 根元素( html 就是一个 bfc )
  • 设置浮动
  • overflow 设置为 auto、scroll、hidden
  • positon 设置为 absolute、fixed
  • display 取值为 inline-block 、 table-cell 、 table-caption 、 flex 、inline-flex 之一的元素

常见的 BFC 应用有:

  • 解决浮动元素令父元素高度坍塌的问题
  • 解决非浮动元素被浮动元素覆盖问题
  • 解决外边距垂直方向重合的问题

如何减少回流对性能的影响

  • 避免设置大量的 style 属性,因为通过设置 style 属性改变结点样式的话,每一次设置都会触发一次 reflow ,所以最好是使用 class 属性。
  • 实现元素的动画,它的position属性,最好是设为 absoulte 或 fixed ,这样不会影响其他元素的布局动画实现的速度的选择。
  • 不要使用 table 布局,因为 table 中某个元素旦触发了 reflow ,那么整个 table 的元素都会触发 reflow 。那么在不得已使用 table 的场合,可以设置 table-layout:auto ;或者是 table-layout:fixed ;这样可以让 table 一行一行的渲染,这种做法也是为了限制reflow的影响范围
  • 如果 CSS 里面有计算表达式,每次都会重新计算一遍,出发一次 reflow 。
  • visibility: hidden,opacity: 0隐藏元素只会重绘。
  • transform 属于合成属性。

清除浮动

image.png

flex 布局如何使用?

flex 是 Flexible Box 的缩写,意为"弹性布局"。指定容器display: flex即可。

容器有以下属性:flex-direction,flex-wrap,flex-flow,justify-content,align-items,align-content。

  • flex-direction属性决定主轴的方向;
  • flex-wrap属性定义,如果一条轴线排不下,如何换行;
  • flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap;
  • justify-content属性定义了项目在主轴上的对齐方式。
  • space-between是两端对齐,在左右两侧没有边距,而space-around是每个 子项目左右方向的 margin 相等,所以两个item中间的间距会比较大。
  • align-items属性定义项目在交叉轴上如何对齐。
  • align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。

项目(子元素)也有一些属性:order,flex-grow,flex-shrink,flex-basis,flex,align-self。

  • order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
  • flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
  • flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
  • flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。
  • flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。
  • align-self 属性允许单个项目有与其他项目不一样的对齐方式,可覆盖 align-items 属性。默认值为 auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场景。

  • 结构: display:none: 会让元素完全从渲染树中消失,渲染的时候不占据任何空间, 不能点击, visibility: hidden:不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见,不能点击 opacity: 0: 不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见,可以点击
  • 继承: display: none和opacity: 0:是非继承属性,子孙节点消失由于元素从渲染树消失造成,通过修改子孙节点属性无法显示。 visibility: hidden:是继承属性,子孙节点消失由于继承了hidden,通过设置visibility: visible;可以让子孙节点显式。
  • 性能: displaynone : 修改元素会造成文档回流,读屏器不会读取display: none元素内容,性能消耗较大 visibility:hidden: 修改元素只会造成本元素的重绘,性能消耗较少读屏器读取visibility: hidden元素内容 opacity: 0 : 修改元素会造成重绘,性能消耗较少

rgba() 和 opacity 的透明效果有什么不同?

  • rgba() 和 opacity 都能实现透明效果,但最大的不同是 opacity 作用于元素,以及元素内的所有内容的透明度;

  • 而 rgba() 只作用于元素的颜色或其背景色。( 设置 rgba 透明的元素的子元素不会继承透明效果 )

如何用 cssjs 实现多行文本溢出省略效果,考虑兼容性

单行:

overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;

多行:

display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3; //行数
overflow: hidden;

兼容:

p{position: relative; line-height: 20px; max-height: 40px;overflow: hidden;}
p::after{content: "..."; position: absolute; bottom: 0; right: 0; padding-left: 40px;
background: -webkit-linear-gradient(left, transparent, #fff 55%);
background: -o-linear-gradient(right, transparent, #fff 55%);
background: -moz-linear-gradient(right, transparent, #fff 55%);
background: linear-gradient(to right, transparent, #fff 55%);
}

居中为什么要使用 transform(为什么不使用 marginLeft/Top

transform:translate(-50%,-50%) 作用是往左往上移动自身长宽的 50%,以使其居于中心位置。与负margin-left和margin-top实现居中不同的是,margin-left必须知道自身的宽高。

transform 属于合成属性(composite property),对合成属性进行 transition/animation 动画将会创建一个合成层(composite layer),这使得被动画元素在一个独立的层中进行动画。通常情况下,浏览器会将一个层的内容先绘制进一个位图中,然后再作为纹理(texture)上传到 GPU,只要该层的内容不发生改变,就没必要进行重绘(repaint),浏览器会通过重新复合(recomposite)来形成一个新的帧。

top/left属于布局属性,该属性的变化会导致重排(reflow/relayout),所谓重排即指对这些节点以及受这些节点影响的其它节点,进行CSS计算->布局->重绘过程,浏览器需要为整个层进行重绘并重新上传到 GPU,造成了极大的性能开销。

介绍下粘性布局(sticky

position 中的 sticky 值是 CSS3 新增的,设置了 sticky 值后,在屏幕范围(viewport)时该元素的位置并不受到定位影响(设置是top、left等属性无效),当该元素的位置将要移出偏移范围时,定位又会变成fixed,根据设置的left、top等属性成固定位置的效果。

sticky 属性值有以下几个特点:

  • 它会产生动态效果,很像relativefixed的结合

  • 该元素并不脱离文档流,仍然保留元素原本在文档流中的位置。元素根据正常文档流进行定位,然后相对它的最近滚动祖先如果祖先元素都不可以滚动,那么是相对于viewport来计算元素的偏移量

  • 当元素在容器中被滚动超过指定的偏移值时,元素在容器内固定在指定位置。亦即如果你设置了top: 50px,那么在sticky元素到达距离相对定位的元素顶部50px的位置时固定,不再向上移动。

  • staticposition属性的默认值。如果省略position属性,浏览器就认为该元素是static定位。topbottomleftright这四个属性无效。元素在文档常规流中当前的布局位置

  • relative表示,相对于默认位置(即static时的位置)进行偏移,即定位基点是元素的默认位置。移动后仍占位置(因此会在此元素未添加定位时所在位置留下空白)。

  • absolute表示,相对于上级元素(一般是父元素)进行偏移,即定位基点是父元素。定位基点(一般是父元素)不能是static定位,否则定位基点就会变成整个网页的根元素html。注意,absolute定位的元素会被移出正常文档流,并不为元素预留空间,周边元素不受影响。

  • fixed表示,相对于视口(viewport,浏览器窗口)进行偏移,即定位基点是浏览器窗口。这会导致元素的位置不随页面滚动而变化。元素会被移出正常文档流,并不为元素预留空间

CSS3transitionanimation 的属性分别有哪些

transition 过渡动画:

  • transition-property:指定过渡的 CSS 属性
  • transition-duration:指定过渡所需的完成时间
  • transition-timing-function:指定过渡函数
  • transition-delay:指定过渡的延迟时间

animation 关键帧动画:

  • animation-name:指定要绑定到选择器的关键帧的名称animation-name
  • animation-duration:动画指定需要多少秒或毫秒完成
  • animation-timing-function:设置动画将如何完成一个周期
  • animation-delay:设置动画在启动前的延迟间隔
  • animation-iteration-count:定义动画的播放次数
  • animation-direction:指定是否应该轮流反向播放动画
  • animation-fill-mode:规定当动画不播放时(当动画完成时,或当动画有一个延迟未开始播放时),要应用到元素的样式
  • animation-play-state:指定动画是否正在运行或已暂停

@keyframes关键帧动画, animation-name填在animation的animation-name属性上。

div {
  animation: animation-name 5s infinite;
}

@keyframes animation-name {
  from {
    transform: translateX(0%);
  }

  to {
    transform: translateX(100%);
  }
}

讲一下png8、png16、png32的区别,并简单讲讲 png 的压缩原理

PNG图片主要有三个类型,分别为 PNG 8/ PNG 24 / PNG 32。

  • PNG 8:PNG 8中的8,其实指的是8bits,相当于用2^8(2的8次方)大小来存储一张图片的颜色种类,2^8等于256,也就是说PNG 8能存储256种颜色,一张图片如果颜色种类很少,将它设置成PNG 8得图片类型是非常适合的。
  • PNG 24:PNG 24中的24,相当于3乘以8 等于 24,就是用三个8bits分别去表示 R(红)、G(绿)、B(蓝)。R(0-255),G(0-255),B(0-255),可以表达256乘以256乘以256=16777216种颜色的图片,这样PNG 24就能比PNG 8表示色彩更丰富的图片。但是所占用的空间相对就更大了。
  • PNG 32:PNG 32中的32,相当于PNG 24 加上 8bits的透明颜色通道,就相当于R(红)、G(绿)、B(蓝)、A(透明)。R(0255),G(0255),B(0255),A(0255)。比PNG 24多了一个A(透明),也就是说PNG 32能表示跟PNG 24一样多的色彩,并且还支持256种透明的颜色,能表示更加丰富的图片颜色类型。

PNG图片的压缩,分两个阶段:

  • 预解析(Prediction):这个阶段就是对png图片进行一个预处理,处理后让它更方便后续的压缩。

  • 压缩(Compression):执行Deflate压缩,该算法结合了 LZ77 算法和 Huffman 算法对图片进行编码。

  • 科普一下 WebpWebP 格式,谷歌(google)开发的一种旨在加快图片加载速度的图片格式。图片压缩体积大约只有 JPEG2/3 ,并能节省大量的服务器带宽资源和数据空间。FacebookEbay 等知名网站已经开始测试并使用 WebP 格式。

  • 在质量相同的情况下, WebP 格式图像的体积要比 JPEG 格式图像小40%

  • 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好

  • 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替

  • 照片使用 JPEG

如何用 CSS 实现一个三角形

可以利用 border 属性

利用盒模型的 border 属性上下左右边框交界处会呈现出平滑的斜线这个特点,通过设置不同的上下左右边框宽度或者颜色即可得到三角形或者梯形。

如果想实现其中的任一个三角形,把其他方向上的 border-color 都设置成透明即可。

div{
width: 0;
height: 0;
border: 10px solid red;
border-top-color: transparent;
border-left-color: transparent;
border-right-color: transparent;
}

如何用 CSS 画曲线

.curve {
    position: absolute;
    width: 250px;
    height: 30px;
    border-bottom: 1px solid red;
    border-right: 1px solid red;
    border-bottom-right-radius: 100%;
    bottom: 0;
  }
 
.curve:after {
    content: '';
    position: absolute;
    width: 250px;
    height: 30px;
    border-top: 1px solid red;
    border-left: 1px solid red;
    border-top-left-radius: 100%;
    left: 250px;
    bottom: 30px;
  }

如何实现一个自适应的正方形

  • 方法1:利用 CSS3 的 vw 单位

vw 会把视口的宽度平均分为 100 份

.square {
 width: 10vw;
 height: 10vw;
 background: red;
}
  • 方法2:利用 margin 或者 padding 的百分比计算是参照父元素的 width 属性
.square {
 width: 10%;
 padding-bottom: 10%; 
 height: 0; // 防止内容撑开多余的高度
 background: red;
}

说说两种盒模型以及区别

盒模型也称为框模型,就是从盒子顶部俯视所得的一张平面图,用于描述元素所占用的空间。它有两种盒模型,W3C盒模型和IE盒模型(IE6以下,不包括IE6以及怪异模式下的IE5.5+)

理论上两者的主要区别是二者的盒子宽高是否包括元素的边框和内边距。当用CSS给给某个元素定义高或宽时,IE盒模型中内容的宽或高将会包含内边距和边框,而W3C标准盒模型并不会。

css盒模型

  • CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括: 外边距(margin) 、 边框 (border) 、 内边距(padding) 、 实际内容(content) 四个属性。 CSS盒模型:标准模型 + IE模型
  • 标准模型: box-sizing: content-box(默认值);width = content width;
  • IE模型:box-sizing: border-box;- width = border + padding + content width

你能描述一下渐进增强和优雅降级之间的不同吗?

渐进增强 (progressive enhancement) :针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。

优雅降级 (graceful degradation) :一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。

区别:优雅降级是从复杂的现状开始,并试图减少用户体验的供给,而渐进增强则是从一个非常基础的,能够起作用的版本开始,并不断扩充,以适应未来环境的需要。降级(功能衰减)意味着往回看;而渐进增强则意味着朝前看,同时保证其根基处于安全地带。

sass,less区别

  • Sass 是基于 Ruby 语言的,而 LESS 和 Stylus 可以基于 NodeJS NPM 下载相应库后进行编译;这也是为什么安装 Sass 的时候有时候会报错,需要安装 python 脚本。

为什么要初始化CSS样式

因为浏览器的兼容问题,不同浏览器对有些标签的默认值是不同的,如果没对 CSS 初始化往往会出现浏览器之间的页面显示差异。

当然,初始化样式会对 SEO 有一定的影响,但鱼和熊掌不可兼得,但力求影响最小的情况下初始化。

image.png

浏览器是如何解析 css 选择器的?

从右向左解析的。若从左向右匹配,发现不符合规则,需要回溯,会损失很多性能。若从右向左匹配,先找到所有的最后节点,对于每一个节点,向上寻找其父节点直到查找至根元素或满足条件的匹配规则,则结束这个分支的遍历。

hybrid app

屏幕适配

vw 和 vh

  • vw 全称是 viewport width,代表的是 视口的宽度,相对于 视口 viewport 的 宽度
  • vh 全称是 viewport height,代表的是 视口的高度,相对于 视口 viewport 的 高度
  • vwvh 是将 视口 宽/高 都分成 100 份,因此 100vw = 视口宽100vh = 视口高
  • vmin 和 vmax 代表的是 视口宽度 和 视口高度 中的 最小值 和 最大值

px、em的区别

  • px 值固定,容易计算。
  • em 值不固定,用于 font-size 中则是相对于 父级元素 font-size 大小,用于 其他属性(如 width,height)  中使用是相对于 自身 font-size 大小。(1em就等于该元素font-size的大小)
  • rem 值不固定,相对于根元素html字体大小会调整,(用于响应式布局); postcss-pxtorem 是一款 PostCSS 插件,用于将 px 单位转化为 rem 单位

image.png

等比缩放 — viewport <meta> 标记

  • 在开发时,直接使用 设计稿提供的数据 px 即可,然后在将整体页面按照 设备视口 / 设计稿 的比例进行 整体缩放。而缩放就需要使用到viewport<meta> 标记来控制视口的大小和形状了。只需要控制其中的 width 和 initial-scale 的值即可,它们分别是代表当前设备视口宽度和缩放比的值。width = 设备视口宽initial-scale = 设备视口 / 设计稿

JSBridge

Bridge是一个桥梁,连接起JS和Native。H5可以通过Bridge调用Native的能力,Native也可以调用JS执行,常见的跨平台框架ReactNative、Cordova都是使用JSBridge处理js和native之间的通信的。WebviewJavascriptBridge这里使用的拦截URL的方式。

  • JS侧如何调用Native侧注册的Bridge

image.png

  • Native调用JS注册的Bridge的逻辑是相似的,不过就不是通过触发iframe的src触发执行的了,因为Native可以自己主动调用JS侧的方法。通过代码可以看出来JS和Native两侧注册的变量和函数都是对称的

image.png

  • 既然webview能拦截到url的变化,为什么要修改iframe的src,而不是直接设置location.href?

通过 location.href 有个问题,就是如果 JS 多次调用原生的方法也就是 location.href 的值多次变化,Native 端只能接受到最后一次请求,前面的请求会被忽略掉,而iframe的多次设置不会出现这个问题,所以这里用了iframe。

开发问题

  • 动画闪白卡顿开启硬件加速
.elem {
    transform: translate3d(0, 0, 0);
    /* transform: translateZ(0); */
}
  • 1px问题在Retina 高清屏上才会出现,由于高清屏用多个物理像素显示一个css像素,比如iphone6,由于dpr为2,所以1css像素会用2个物理像素显示,所以看起来1px的线条会特别宽。
.elem {
    position: relative;
    width: 200px;
    height: 80px;
    &::after {
        position: absolute;
        left: 0;
        top: 0;
        border: 1px solid #f66;
        width: 200%;
        height: 200%;
        content: "";
        transform: scale(.5);
        transform-origin: left top;
    }
}
  • 移动端浏览器点击延迟里点击操作会存在300ms延迟,往往会造成点击延迟甚至点击无效。2007年苹果发布首款iPhone搭载的Safari为了将桌面端网站能较好地展示在移动端浏览器上而使用了双击缩放。该方案就是上述300ms延迟的主要原因,当用户执行第一次单击后会预留300ms检测用户是否继续执行单击,若是则执行缩放操作,若否则执行点击操作。

    • 禁止页面缩放<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
    • 更改默认的视口宽度<meta name="viewport" content="width=device-width" />.这个方案相比方案一的好处在于,它没有完全禁用缩放,而只是禁用了浏览器默认的双击缩放行为,但用户仍然可以通过双指缩放操作来缩放页面。
    • zepto封装tap事件能有效地解决点击穿透,我们知道移动端点击事件触发的顺序为 touchstart —> touchmove —> touchend —> click,通过监听document上的touch事件(因为click事件在移动端有300ms延迟但是touch事件在移动端没延迟)完成tap事件的模拟,并将tap事件冒泡到document上触发模拟移动端的click事件。虽然zepto能解决点击延迟问题,但是也会带来点击穿透问题,就是如果某元素下面真实有绑定click的元素,我们知道事件具有传播性,就会在tap事件后会继续触发click,这样就会带来点击穿透问题。(在touchend事件里面使用e.preventDefault()来阻止后续mouse和click事件触发)
    • fastclick 是 FT Labs 专门为解决移动端浏览器 300 毫秒点击延迟问题所开发的一个轻量级的库。fastclick的实现原理是在检测到touchend事件的时候调用 event.preventDefault,会通过DOM自定义事件立即通过 document.createEvent 创建一个 MouseEvents,然后 通过 event​Target​.dispatch​Event 触发对应目标元素上绑定的 click 事件。并把浏览器在300ms之后的click事件阻止掉。fastclick不但能解决点击延迟而且不会带来点击穿透问题,是点击延迟的最优解。
  • 禁止滑动穿透移动端浏览器里出现弹窗时,若在屏幕上滑动能触发弹窗底下的内容跟着滚动。当打开弹窗时给<body>声明position:fixed;left:0;width:100%并动态声明top。声明position:fixed会导致<body>滚动条消失,此时会发现虽然无滑动穿透,但页面滚动位置早已丢失。通过scrollingElement获取页面当前滚动条偏移量并将其取负值且赋值给top,那么在视觉上就无任何变化。当关闭弹窗时移除position:fixed;left:0;width:100%和动态top

// css
body.static { position: fixed; left: 0; width: 100%; }


// js
const body = document.body;
const openBtn = document.getElementById("open-btn");
const closeBtn = document.getElementById("close-btn");
openBtn.addEventListener("click", e => {
    e.stopPropagation();
    const scrollTop = document.scrollingElement.scrollTop;
    body.classList.add("static");
    body.style.top = `-${scrollTop}px`;
});
closeBtn.addEventListener("click", e => {
    e.stopPropagation();
    body.classList.remove("static");
    body.style.top = "";
});
  • 支持弹性滚动在苹果系统上非<body>元素的滚动操作可能会存在卡顿,但安卓系统不会出现该情况。通过声明overflow-scrolling:touch调用系统原生滚动事件优化弹性滚动,增加页面滚动的流畅度。
body {
    -webkit-overflow-scrolling: touch;
}
.elem {
    overflow: auto;
}
  • 禁止滚动传播桌面端浏览器不一样,移动端浏览器有一个奇怪行为。当页面包含多个滚动区域时,滚完一个区域后若还存在滚动动量则会将这些剩余动量传播到下一个滚动区域,造成该区域也滚动起来。这种行为称为滚动传播
.elem {
    overscroll-behavior: contain;
}
  • 支持往返刷新 往返缓存指浏览器为了在页面间执行前进后退操作时能拥有更流畅体验的一种策略,以下简称BFCache。该策略具体表现为:当用户前往新页面前将旧页面的DOM状态保存在BFCache里,当用户返回旧页面前将旧页面的DOM状态从BFCache里取出并加载。大部分移动端浏览器都会部署BFCache,可大大节省接口请求的时间和带宽。

    • 在新页面监听页面销毁事件onunload
    • pageshow事件在每次页面加载时都会触发,无论是首次加载还是再次加载都会触发,这就是它与load事件的区别。pageshow事件暴露的e.persisted可判断页面是否从BFCache里取出。
  • 苹果日期解析问题 在苹果系统上解析YYYY-MM-DD HH:mm:ss这种日期格式会报错Invalid Date,但在安卓系统上解析这种日期格式完全无问题。查看Safari相关开发手册发现可用YYYY/MM/DD HH:mm:ss这种日期格式。

  • 修复键盘输入后高度坍塌 键盘占位会把页面高度压缩一部分。当输入完成键盘占位消失后,页面高度有可能回不到原来高度,产生坍塌导致Webview底色露脸,简单概括就是输入框失焦后页面未回弹。

const input = document.getElementById("input");
let scrollTop = 0;
input.addEventListener("focus", () => {
    scrollTop = document.scrollingElement.scrollTop;
});
input.addEventListener("blur", () => {
    document.scrollingElement.scrollTo(0, scrollTop);
});
  • 修复输入监听 在苹果系统上的输入框输入文本,keyup/keydown/keypress事件可能会无效。当输入框监听keyup事件时,逐个输入英文和数字会有效,但逐个输入中文不会有效,需按回车键才会有效。此时可用input事件代替输入框的keyup/keydown/keypress事件

  • 简化回到顶部 该函数就是scrollIntoView,它会滚动目标元素的父容器使之对用户可见

  • 简化懒性加载 该函数就是IntersectionObserver,它提供一种异步观察目标元素及其祖先元素或顶级文档视窗交叉状态的方法。(懒性加载的第二种使用场景:下拉加载。在列表最底部部署一个占位元素且该元素无任何高度或实体外观,只需确认占位元素进入可视区域就请求接口加载数据。)

const imgs = document.querySelectorAll("img.lazyload");
const observer = new IntersectionObserver(nodes => {
    nodes.forEach(v => {
        if (v.isIntersecting) { // 判断是否进入可视区域
            v.target.src = v.target.dataset.src; // 赋值加载图片
            observer.unobserve(v.target); // 停止监听已加载的图片
        }
    });
});
imgs.forEach(v => observer.observe(v));
  • 唤醒原生应用或与原生应用通信 通过location.href与原生应用建立通讯渠道,这种页面与客户端的通讯方式称为URL Scheme,其基本格式为scheme://[path][?query]URL Scheme一般由前端与客户端共同协商。<a href="alipays://platformapi/startapp?saId=10000007">打开支付宝的扫一扫</a>

canvas

  • <canvas> 元素可被用来通过 JavaScript( Canvas APIWebGL API )绘制图形及图形动画,也就是说 <canvas> 标签只是一个图形容器。Canvas 可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。Canvas API 主要聚焦于 2D 图形。而 WebGL API 则用于绘制硬件加速的 2D 和 3D 图形。
  • <canvas> 标签只有两个属性—— width 和 height,设置画布尺寸。通过css设置的是canvas元素尺寸。此元素尺寸非彼画布尺寸,如果元素尺寸和画布尺寸比例不一样,绘制出来的图像是扭曲的。(元素尺寸只设置widthheight自动适配画布比列)
  • canvas 的坐标体系(x,y),原点 (0,0) 在左上角。x轴向右,y轴向下。
  • 绘制图形
    • fillRect(x, y, width, height);绘制一个填充的矩形,填充色为当前的 fillStyle。(设置一个背景色)
    • strokeRect(x, y, width, height);绘制一个矩形的边框,边框色为当前的 strokeStyle
    • clearRect(0, 0, canvas.width, canvas.height);通过把像素设置为透明,以达到擦除一个矩形区域的效果。(清除整个画布)
    • 请确保在调用 clearRect() 之后绘制新内容前调用 beginPath() 。
  • 路径
    • 第一步,创建路径起始点,beginPath()(清空路径列表)
    • 第二步,画出路径。
    • 第三步(可选) ,闭合路径(路径生成),closePath()(绘制一条从当前点到开始点的直线来闭合图形)。
    • 第四步,通过描边或填充路径区域来渲染图形,调用 stroke(),不会自动闭合 / 调用 fill(),所有没有闭合的形状都会自动闭合。。
<canvas id="canvas" width="300" height="150"></canvas>

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d", { alpha: false }); // 得到渲染上下文

// 描边三角形 
ctx.beginPath(); 
ctx.moveTo(125, 25); // 将笔触移动到坐标 (x,y) 上,设置起点,绘制一些不连续的路径
ctx.lineTo(125, 105); // 绘制直线的,绘制一条从当前位置到指定坐标 (x,y) 的直线。
ctx.lineTo(205, 25); 
ctx.closePath(); 
// 手动闭合路径 
ctx.stroke();

// 阴影
ctx.shadowOffsetX = 10; 
ctx.shadowOffsetY = 10; 
ctx.shadowBlur = 10; 
ctx.shadowColor = "rgba(23,23,23,0.5)"; 
ctx.fillRect(20, 20, 150, 150);

  • 圆弧
    • 绘制圆弧或者圆,可以选择 arc() 或者 arcTo()。两个方法的绘制思路是不一样的,我们通常选择 arc(),因为更可控。arc(x, y, radius, startAngle, endAngle, anticlockwise);
  • Path2D
    • Path2D 可以用缓存或记录绘画命令,就像是把某一段路径抽成一个函数用于复用,这样做也简化了代码和提高了性能。比如 moveTorectarc 或 quadraticCurveTo 等。
    • Path2D API 有另一个强大的功能,可以使用 SVG path data 来初始化 canvas 上的路径,这意味着可以将 SVG 搬上 canvas。
    • addPath 可以将 path 结合起来(添加一条路径到当前路径),这很适用从几个元素中来创建对象。
var path1 = new Path2D();  // 创建空的Path对象记录路径
path1.rect(10, 10, 100, 100);

var path2 = new Path2D(path1); // 克隆Path对象缓存路径
path2.moveTo(220, 60);
path2.arc(170, 60, 50, 0, 2 * Math.PI);

ctx.stroke(path2);

// SVG path
var p = new Path2D("M10 10 h 80 v 80 h -80 Z"); 
ctx.fill(p);

  • 填充对象
    • 渐变对象。用 线性ctx.createLinearGradient(x1, y1, x2, y2); 或者 径向ctx.createRadialGradient(x1, y1, r1, x2, y2, r2); 的两种渐变形式来新建一个 canvasGradient 对象,并且赋给图形的 fillStyle 或 strokeStyle 属性。
    • 图案对象。图案的应用跟渐变很类似的,ctx.createPattern(image | canvas, "repeat");创建出一个 pattern 之后,赋给 fillStyle 或 strokeStyle 属性即可。
// 创建渐变
var ball = ctx.createRadialGradient(45, 45, 10, 52, 50, 30);
ball.addColorStop(0, "#A7D30C");
ball.addColorStop(0.9, "#019F62");
ball.addColorStop(1, "rgba(1,159,98,0)");
ctx.fillStyle = ball;
ctx.fillRect(0, 0, 150, 150);

// 创建新 image 对象,用作图案 
let img = new Image(); 
img.src = "test.png"; 
// 确认 image 对象加载完毕 
img.onload = function () { 
  // 创建图案 
  let ptrn = ctx.createPattern(img, "repeat"); 
  ctx.fillStyle = ptrn; 
  ctx.fillRect(0, 0, 150, 150); 
};
  • 绘制文本

    • 描述当前字体样式,ctx.font = "bold 48px serif";
    • fillText(text, x, y [, maxWidth]) 在指定的 (x,y) 位置填充指定的文本,绘制的最大宽度是可选的。
    • strokeText(text, x, y [, maxWidth]) 在指定的 (x,y) 位置绘制文本边框,绘制的最大宽度是可选的。
  • 绘制图像

    • ctx.drawImage(image|video|canvas, x, y, width, height);
    • img.onload事件内drawImage
    • video.onloadeddata,video.onpause事件内drawImage,canvas 会绘制当前视频帧
    • ctx.drawImage(canvas, x, y, width, height);一个常用的应用就是将第二个 canvas 作为第一个 canvas 的缩略图
  • 状态的保存和恢复

    • save():保存画布的所有状态。restore():恢复保存的画布状态。
    • 画布的状态被保存在一个栈中。一个绘画状态包括属性,当前变形,当前裁剪路径。
    • 保存和恢复可以多次调用,save()保存的状态是可以多次保存的,同时保存在栈中的元素遵循的是后进先出的顺序;需要注意的是每一次调用 restore 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。
  • 变形

    • translate(x, y),移动 canvas 和它的原点到一个不同的位置。
    • rotate(angle)以canvas原点为中心旋转 canvas。angle 取正时,顺时针旋转;angle 取负时,逆时针旋转。
    • scale(x, y),对形状,位图进行缩小或者放大。x和y的值小于1则为缩小,大于1则为放大。默认值为 1。如果是负数,相当于以 x 或 y 轴作为对称轴镜像反转。
    • 变形矩阵transform(a, b, c, d, e, f)
      • a:水平方向的缩放
      • b:竖直方向的倾斜偏移
      • c:水平方向的倾斜偏移
      • d:竖直方向的缩放
      • e:水平方向的移动
      • f:竖直方向的移动
    • resetTransform();重置矩阵 重置当前变形为单位矩阵,等同于 ctx.setTransform(1, 0, 0, 1, 0, 0);
    • ctx.setTransform(a, b, c, d, e, f);重置并修改矩阵 将当前的变形矩阵重置为单位矩阵,然后用相同的参数调用 transform 方法。
  • 合成与裁剪

    • 合成的图形受限于绘制的顺序。如果我们不想受限于绘制的顺序,那么我们可以利用 globalCompositeOperation 属性来改变这种情况。ctx.globalCompositeOperation = 'source-over'
    • clip() 方法其实是绘制图形(stroke()fill())的第三个进阶方法,clip() 会将当前正在构建的路径转换为当前的裁剪路径,所有在路径以外的部分都会被隐藏。
  • ImageData 对象

    • ImageData 对象中存储着 canvas 对象真实的像素数据,widthheight:图片宽度和高度,单位是像素。data:Uint8ClampedArray 类型的一维数组,每四个值代表一个像素,按照红,绿,蓝和透明值的顺序。
    • 可通过修改ImageData.data数组内每4个值组成一个像素,修改像素颜色。
    • 创建像素数据:ctx.createImageData(width, height);ctx.createImageData(anotherImageData);
    • 获得像素数据:ctx.getImageData(left, top, width, height);借助鼠标事件mousedown,根据鼠标的位置和 getImageData 获取当前的像素信息实现拾色器。
    • 改写像素信息:ctx.putImageData(myImageData, dx=0, dy=0);``dx,dy 代表你希望绘制图像的左上角位置。
var invert = function () {
  ctx.drawImage(img, 0, 0);
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  const data = imageData.data;
  for (var i = 0; i < data.length; i += 4) {
    data[i] = 255 - data[i]; // red
    data[i + 1] = 255 - data[i + 1]; // green
    data[i + 2] = 255 - data[i + 2]; // blue
  }
  ctx.putImageData(imageData, 0, 0);
};
invert();
  • 保存图片

    • canvas.toDataURL("image/png"|"image/jpeg", quality); 选择从 0 到 1 的品质量,1 表示最好品质,0 基本不被辨析但有比较小的文件大小。
  • 动画

    • 定时执行重绘的方法
      • setInterval(function, delay):适合不需要交互的场景
      • setTimeout(function, delay):通过键盘或者鼠标事件来捕捉用户的交互,再用 setTimeout 执行相应的动作
      • requestAnimationFrame(callback):这个方法更加平缓并更加有效率,当系统准备好了重绘条件的时候,才调用绘制动画帧。
    • 绘制动画每一帧的步骤
      • 清空 canvas:比如 clearRect 方法(调整 canvas 大小
      • 保存 canvas 状态:save()。如果要改变 canvas 状态的设置(样式,变形之类的),之后又要在每画一帧之时都是原始状态的情况时,需要先保存一下。
      • 绘制动画帧
      • 恢复 canvas 状态:restore()。如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧。
    • 长尾效果,用带透明度的矩形代替清空ctx.clearRect();
      • ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
      • ctx.fillRect(0, 0, canvas.width, canvas.height);
  • canvas 的优化

    • 避免浮点数的坐标点,用整数取而代之
    • 不要在用 drawImage 时缩放图像,  可以缓存图片的不同尺寸
    • 使用多层画布去画一个复杂的场景,比如静态背景和频繁发生移动的对象
    • 用 CSS 设置大的背景图,避免在每一帧在画布上反复地绘制大图。
    • 用 CSS transforms 特性缩放画布 CSS transforms 使用 GPU 速度会更快。当然最好是不直接缩放画布,或者让小的画布按比例放大,而不是大的画布按比例缩小。
    • 关闭透明度{ alpha: false }
    • 将画布的函数调用集合到一起(例如,画一条折线,而不要画多条分开的直线)
    • 避免不必要的画布状态改变渲染画布中的不同点,而非整个新状态
    • 尽可能避免 shadowBlur 特性避免 text rendering
    • 不同的场景可以选择不同的清除画布的方法clearRect()fillRect()、调整 canvas 大小
    • 使用 window.requestAnimationFrame() 而非 window.setInterval()

three.js

  • 场景(scene)
    • 场景是一个三维空间, 所有物品的容器。相当于世界, 我们所创造的所有物体光源等都必须添加到场景中才能生效或可见。
    • 如何更新场景?
      • 默认情况下,所有对象都会自动更新它们的矩阵(如果它们已添加到场景中)
      • 如果你知道对象将是静态的,则可以禁用此选项object.matrixAutoUpdate = false;并在需要时手动更新转换矩阵object.updateMatrix();
      • 如果你知道BufferGeometry的一个属性会增长,比如顶点的数量, 你必须预先分配足够大的buffer来容纳可能创建的任何新顶点。 当然,这也意味着BufferGeometry将有一个最大大小 —— 无法创建一个可以高效地无限扩展的BufferGeometry。我们以在渲染时扩展的line来示例。我们将分配可容纳500个顶点的空间但起初仅绘制2个,使用 在500个顶点的缓冲区中
      • 材质(Materials)所有uniforms值都可以自由改变(比如 colors, textures, opacity 等等),这些数值在每帧都发给shader。GL状态相关参数也可以随时改变(depthTest, blending, polygonOffset 等
        • 在运行时无法轻松更改以下属性(一旦material被渲染了一次),这些变化需要建立新的shader程序。你需要设置material.needsUpdate = true
        • uniforms的数量和类型
        • 是否存在texture,fog vertex colors,morphing,shadow map,alpha test,transparent。
        • 请记住,这可能会非常缓慢并导致帧率的波动。(特别是在Windows上,因为shader编译在directx中比opengl慢)。
      • 纹理(Textures)如果更改了图像,画布,视频和数据纹理,则需要设置以下标志:texture.needsUpdate = true;渲染对象就会自动更新。
      • 相机(Cameras)相机的位置和目标会自动更新。如果你需要改变fov,aspect,near,far,那么你需要重新计算投影矩阵camera.aspect = window.innerWidth/window.innerHeight;camera.updateProjectionMatrix()
    • 在threejs中,场景是右手坐标系, 把右手放在原点的位置,使大拇指,食指和中指互成直角,把大拇指指向x轴的正方向,食指指向y轴的正方向时,中指所指的方向就是z轴的正方向。
    • image.png
const initScene = () => {
    scene = new THREE.Scene();   

    grid = new THREE.GridHelper(300, 150, 0xbbbbbb, 0xbbbbbb);
    grid.material.transparent = true;
    grid.material.opacity = 0.6;
    scene.add(grid);

    group = new THREE.Group();
    scene.add(group);

    new RGBELoader().load('/test/test.hdr', (texture) => {
      texture.mapping = THREE.EquirectangularReflectionMapping;
      scene.background = texture;
      scene.background = new THREE.Color(0xefefef);
      scene.fog = new THREE.Fog(0xefefef, 80, 100);
      scene.environment = texture;
      scene.rotation.y = -Math.PI / 2;
    });
  };
  • 相机(camera)
    • 相机决定了场景中那个角度的景色会显示出来。相机就像人的眼睛一样,人站在不同位置,抬头或者低头都能够看到不同的景色。值得注意的是场景只有一种,但是相机却有很多种。
    • 透视相机PerspectiveCamera是模拟人眼的视觉,近大远小(透视)
    • 正交投影相机OrthographicCamera是无论物体距离相机距离远或者近,在最终渲染的图片中物体的大小都保持不变。我们所所熟知的三视图就是很典型的正交投影模式,多运用与工程制图
const initCamera = () => {
    camera = new THREE.PerspectiveCamera(30, cameraWidth / cameraHeight, 0.1, 100);

    camera.position.set(30, 30, 30);
    camera.updateMatrix();
    scene.add(camera);
  };
  • 渲染器(renderer)
    • 渲染器决定了渲染的结果应该画在页面的什么元素上面,并且以怎样的方式来绘制。
    • 渲染器renderer的domElement元素,表示渲染器中的画布,所有的渲染都是画在domElement上的
const initRenderer = () => {
    container = document.getElementById('canvas3d');

    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(cameraWidth, cameraHeight);
    renderer.outputEncoding = THREE.sRGBEncoding;
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 4;
    container.appendChild(renderer.domElement);
  };
  • 轨道控制器(OrbitControls)
    • 使用控制器实现移动、改变视角、旋转、缩放等预览效果。
const initControls = () => {
    controls = new OrbitControls(camera, container);
    controls.enableZoom = true;
    controls.enableDamp = true;
    controls.enableRotate = true;
    controls.minDistance = 1;
    controls.maxDistance = 75;
    controls.maxPolarAngle = Math.PI;
  };
  • 后期处理(postprocessing)
    • 后期处理是一种被广泛使用、用于来实现如景深、发光、胶片微粒或是各种类型的抗锯齿等这些效果的方式。 首先,场景被渲染到一个渲染目标上,渲染目标表示的是一块在显存中的缓冲区。 接下来,在图像最终被渲染到屏幕之前,一个或多个后期处理过程将滤镜和效果应用到图像缓冲区。
const initPass = () => {
    composer = new EffectComposer(renderer);

    renderPass = new RenderPass(scene, camera);
    renderPass.clear = false;

    outlinePass = new OutlinePass(
    new THREE.Vector2(cameraWidth, cameraHeight), scene, camera);
    outlinePass.edgeStrength = 10;
    outlinePass.edgeThickness = 0.1;
    outlinePass.visibleEdgeColor.set(0xff2200);

    bloomPass = new UnrealBloomPass(
    new THREE.Vector2(cameraWidth, cameraHeight), 1.5, 0.4, 0.85);
    bloomPass.threshold = bloomParams.bloomThreshold;
    bloomPass.strength = bloomParams.bloomStrength;
    bloomPass.radius = bloomParams.bloomRadius;
    bloomPass.oldClearAlpha = 1;

    composer.addPass(renderPass);
    composer.addPass(outlinePass);
  };
  • 渲染循环(requestAnimationFrame)
const animate = () => {
    TWEEN.update();

    requestAnimationFrame(animate);
    composer.render();
  };
  • 光线投射(Raycaster)
    • 射线拾取功能在你鼠标点击的位置发射一根射线,被这根射线射中的物体都被记录下来。然后在根据被记录下来的物体去判断点中的物体。
renderer.domElement.addEventListener('click', function (event) {
    // .offsetY、.offsetX以canvas画布左上角为坐标原点,单位px
    const px = event.offsetX;
    const py = event.offsetY;
    //屏幕坐标px、py转WebGL标准设备坐标x、y
    //width、height表示canvas画布宽高度
    const x = (px / width) * 2 - 1;
    const y = -(py / height) * 2 + 1;
    //创建一个射线投射器`Raycaster`
    const raycaster = new THREE.Raycaster();
    //.setFromCamera()计算射线投射器`Raycaster`的射线属性.ray
    // 形象点说就是在点击位置创建一条射线,射线穿过的模型代表选中
    raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
    //.intersectObjects([mesh1, mesh2, mesh3])对参数中的网格模型对象进行射线交叉计算
    // 未选中对象返回空数组[],选中一个对象,数组1个元素,选中两个对象,数组两个元素
    const intersects = raycaster.intersectObjects([mesh1, mesh2, mesh3]);
    console.log("射线器返回的对象", intersects);
    // intersects.length大于0说明,说明选中了模型
    if (intersects.length > 0) {
        // 选中模型的第一个模型,设置为红色
        intersects[0].object.material.color.set(0xff0000);
    }
})
  • 精灵模型(Sprite)
    • Sprite的特点是无论三维场景旋转到什么角度,sprite所制作的元素都是正对着相机,即Sprite矩形平面会始终平行于Canvas画布或者说屏幕。
cont initSprite = () => {
   const texture = new THREE.TextureLoader().load(url) 
   const material = new THREE.SpriteMaterial({ map: texture }) 
   const sprite = new THREE.Sprite(material) 
   // 设置大小、位置、内容   
   sprite.scale.set(0.5, 0.5, 0.5) 
   sprite.position.set(0.4, 0, -4.5) 
   // 加入场景中 
   scene.add(sprite)
}
  • 网格(mesh)由几何体(geometry)和材质(material)构成
    • 使用.TextureLoader()加载图片,转化为纹理,通过属性map设置材质纹理。就实现了简单的纹理加载。
    // 添加立方体
    const geometry = new THREE.BoxGeometry(10, 10, 10)
    // 左右、上下、后前
    const urls = [
        'https://cdn.huodao.hk/upload_img/20220620/3e532822bd445485d27677ca55a79b10.jpg?proportion=1',
        'https://cdn.huodao.hk/upload_img/20220620/cebf6fbcafdf4f5c945e0881418e34ec.jpg?proportion=1',
        'https://cdn.huodao.hk/upload_img/20220620/273081d1896fc66866842543090916d3.jpg?proportion=1',
        'https://cdn.huodao.hk/upload_img/20220620/8747f61fd2215aa748dd2afb6dce3822.jpg?proportion=1',
        'https://cdn.huodao.hk/upload_img/20220620/c34262935511d61b2e9f456b689f5c1c.jpg?proportion=1',
        'https://cdn.huodao.hk/upload_img/20220620/722d2bf88f6087800ddf116511b51e73.jpg?proportion=1'
    ]
    const boxMaterial = []

    urls.forEach((item) => {
        // 纹理加载
        const texture = new THREE.TextureLoader().load(item)
        // 通过旋转修复天花板和地板
        if (item == '4_u' || item == '4_d') {
        texture.rotation = Math.PI
        texture.center = new THREE.Vector2(0.5, 0.5)
        }
        // 创建材质
        boxMaterial.push(new THREE.MeshBasicMaterial({ map: texture }))
    })
    const house = new THREE.Mesh(geometry, boxMaterial)
    house.geometry.scale(1, 1, -1)
    scene.add(house)
  • 加载模型,清除模型
    • 为何three.js不能够自动废置对象? three.js并不知道用户所创建的实体(例如几何体或者材质)的生命周期或作用范围,这些是应用程序的责任。 比如说,即使一个材质当前没有被用于渲染,但它也可能是下一帧所必需的。 因此,如果应用程序决定某个对象可以被删除,则它必须通过调用对应的dispose() 方法来通知引擎。
    • 将一个mesh(网格)从场景中移除,是否也会废置它的geometry(几何体)和material(材质)? 并不会,你必须通过dispose() 来明确地废置geometry(几何体)或material(材质)。 请记住,geometry(几何体)或material(材质)可以在3D物体之间(例如mesh(网格))被共享。
    • 对于纹理的内部资源仅在图像完全被加载后才会分配。如果你在图像被加载之前废置纹理,什么都不会发生。 没有资源被分配,因此也没有必要进行清理。
    • 当我在调用dispose() 之后,被删除掉的内部资源会被引擎重新创建,因此不会有运行时错误发生,但你可能会注意到这会对当前帧的性能有一些影响,特别是当着色器程序被编译的时候。
    • 可以评估WebGLRenderer.info —— 渲染器中的一个特殊属性,具有一系列关于显存和渲染过程的统计信息。 除此之外,它还告诉你有多少纹理、几何体和着色器程序在内部存储。 如果你在你的应用程序中注意到了性能问题,一个较好的方法便是调试该属性,以便轻松识别内存泄漏。
// 加载模型
const loadModel = (path) => {
    new Promise((resolve, reject) => {
      const loader = new STLLoader();
      loader.load(path,
        (geometry) => {
          geometry.name = GEOMETRYNAME;
          resolve(geometry);
        },
        (xhr) => {
          console.log((xhr.loaded / xhr.total) * 100 + '% loaded');
        },
        (error) => {
          resolve(error);
        },
      );
    });
}

// 清除模型
const clearModelByName = (model, name) => {
    const list = [];
    model.traverse((obj) => {
      if ((obj.isMesh && obj.geometry.name === name) 
      || (obj.name && obj.name === 'modelText')) {
        list.push(obj);
      }
    });
    model.remove(...list);
  };
const clearAll = () => {
    scene && scene.clear && scene.clear();

    if (container) {
      document.body.removeChild(container);
    }
};
  • 文字
const initText = async (name, x, y) => {
    if (!fontResource) {
      fontResource = await new Promise((resolve) => {
        new FontLoader().load('/3d/font.json', (font) => resolve(font));
      });
    }

    const textGeometry = new TextGeometry(name, {
      font: fontResource,
      size: 0.2,
      height: 0,
    });
    const textMesh = new THREE.Mesh(textGeometry, modelGrayMaterial);
    textMesh.position.set(x - 0.5, 0, y + 1.5);
    textMesh.name = 'modelText';

    group.add(textMesh);
  };
  • 灯光(light)

image.png

  • 3d渲染优化
    • 渲染层级总体分为两层:3D 场景层和 HUD 层。3D 场景层顾名思义渲染 3D 场景,由人物模型、建筑模型和宝箱这些互动模型组成;HUD 层渲染互动按钮、弹窗、业务需要的商品列表等2D UI 内容。在 HUD 层上再渲染 3D 内容时,则不得不再增加一层 3D渲染层。
      • 在展示另外的 3D 渲染层时再实例化,并暂停原来 3D 渲染层的渲染
      • 在不需要展示的时候销毁,恢复原 3D 渲染层的渲染方法调用
    • 模型格式选择了 .gltf 格式。相对于其他模型格式,.gltf 可以减少 3D 格式中与渲染无关的的冗余数据,从而确保文件体积更小。
    • 在 C4D 建模完成后,导出 FBX 格式的文件,再导入到对 gltf 支持较好的 blender 软件中,设计师可以预览他们的材质在中转过程中有没有丢失效果,blender 导出的 gltf 文件中的模型也能保持一致的大小。
    • 预设光影。在查阅了渲染原理后,发现当每在一个平面上增加影子,相当于多渲染一次场景,渲染的压力成倍增加。定在地板的贴图纹理上预先加上建筑的投影。
    • 压缩纹理。在项目中使用的资源体积最大的是模型 gltf 文件,检查文件的内容,占体积很大一部分的是纹理贴图,根据 WebGL 渲染原理,无论贴图的资源原来是什么格式,最后在渲染前需要解压。使用 gltf-transform 工具做缩小贴图分辨率,和转换格式的工作。
    • 模型减面。模型在 WebGL 中渲染的流程是先用模型的顶点信息确定三角面,再在每个三角面上计算需要展示的颜色。所以如果能减少模型面的数量,能减少每次渲染的计算量,减少每帧需要的渲染时间。
    • 合批渲染。在前端加载模型文件时,可以遍历模型中的网格 mesh ,把使用相同材质的做合并。CPU 会把这些面合并成相同的画图指令,令对GPU的draw call 数量减少。
    • 懒加载策略:在镜头移动到足够靠近时再加载并插入模型到场景,销毁离镜头足够远的模型。
    • 分级加载策略:在镜头较远时,加载较低精度的模型,较近时再切换成精度高的模型。
    • 分级渲染:能实时检测运行帧率决定是否降级渲染。