Implementing feature detection

 

功能检测包括确定浏览器是否支持特定代码块,并根据是否支持(或不支持)代码运行不同的代码,以便浏览器始终可以提供工作体验,而不会在某些浏览器中崩溃或出错. 本文详细介绍了如何编写自己的简单功能检测,如何使用库来加快实现速度以及用于功能检测的本机功能,例如@supports .

Prerequisites: 熟悉核心的HTMLCSSJavaScript语言; 跨浏览器测试高级原则的想法.
Objective: 了解特征检测的概念,并能够在CSS和JavaScript中实现合适的解决方案.

The concept of feature detection

特征检测背后的想法是,你可以运行测试,以确定在当前浏览器功能是否支持,并有条件地运行的代码都在支持该功能的浏览器,而不要浏览器提供可接受的经验. 如果您不这样做,那么不支持您在代码中使用的功能的浏览器将无法正确显示您的网站,并且只会失败,从而带来糟糕的用户体验.

让我们回顾一下在处理常见JavaScript问题时所涉及的示例-Geolocation API (公开了运行网络浏览器的设备的可用位置数据)具有使用的主要入口点, geolocation属性可在全局导航器对象. 因此,您可以使用以下类似方法检测浏览器是否支持地理定位:

if ("geolocation" in navigator) {
  navigator.geolocation.getCurrentPosition(function(position) {
    // show the location on a map, perhaps using the Google Maps API
  });
} else {
  // Give the user a choice of static maps instead perhaps
}

但是,最好使用已建立的特征检测库,而不要一直编写自己的特征检测库. Modernizr是功能检测测试的行业标准,稍后我们将进行介绍.

在继续进行之前,我们想先说一件事-不要将功能检测与浏览器嗅探 (检测哪个特定的浏览器正在访问该网站)相混淆-这是一个可怕的做法,应该不惜一切代价避免. 有关更多详细信息,请参见使用不良的浏览器嗅探代码 .

Writing your own feature detection tests

在本节中,我们将研究如何在CSS和JavaScript中实现自己的功能检测测试.

CSS

您可以通过测试JavaScript中是否存在element.style.property (例如, paragraph.style.transform .style.transform )来编写CSS功能测试.

一个经典的例子可能是在浏览器中测试Flexbox支持. 对于支持最新Flexbox规范的浏览器,我们可以使用灵活而强大的Flex布局. 对于不支持此功能的浏览器,我们可以使用浮动布局,该布局可以正常运行,尽管它稍微更脆弱,更容易破解,而且外观也不那么酷.

让我们实现一些可以证明这一点的方法,尽管我们现在将其保持简单.

  1. 首先制作css-feature-detect.htmlflex-layout.cssfloat-layout.cssbasic-styling.css文件的本地副本. 将它们保存在新目录中.
  2. 我们也将HTML5 Shiv添加到我们的示例中,以便HTML5语义元素将在IE的较早版本中正确设置样式. 下载最新版本(请参阅手动安装 ),解压缩ZIP文件,将html5shiv-printshiv.min.jshtml5shiv.min.js文件复制到示例目录中,然后通过将以下内容放在<title>元素:
      <script src =" html5shiv.min.js"> </ script> 
  3. 看一下示例CSS文件-您将看到basic-styling.css处理了我们要提供给每个浏览器的所有样式,而其他两个CSS文件包含了我们希望根据具体情况有选择地应用于浏览器的CSS他们的支持水平. 您可以通过手动更改第二个<link>元素所引用的CSS文件来查看这两个文件所具有的不同效果,但是让我们实现一些JavaScript以根据需要自动交换它们.
  4. 首先,删除第二个<link>元素的href属性的内容. 稍后我们将动态填充它.
  5. 接下来,在您的正文底部(恰好在</body>标记之前)添加一个<script></script>元素.
  6. 提供以下内容:
      const conditional = document.querySelector('.conditional');
     const testElem = document.createElement('div');
     如果(testElem.style.flex!==未定义&& testElem.style.flexFlow!==未定义){
       conditional.setAttribute('href','flex-layout.css');
     }其他{
       conditional.setAttribute('href','float-layout.css');
     } 

在这里,我们获取对第二个<link>元素的引用,并创建一个<div>元素作为测试的一部分. 在条件语句中,我们测试浏览器中是否存在flexflex-flow属性. 请注意,存储在HTMLElement.style对象内的那些属性的JavaScript表示形式如何使用小写驼峰字母(而不是连字符)来分隔单词.

注意 :如果您无法正常工作,可以将其与我们的css-feature-detect-finished.html代码进行比较(另请参见实时版本 ).

保存所有内容并尝试示例时,如果浏览器支持现代的flexbox,则应该看到flexbox布局已应用于页面,如果不支持,则应该显示float布局.

注意 :这种方法通常对于一个较小的功能检测问题来说是过大的杀伤力-您通常可以避免使用多个供应商前缀和后备属性,如CSS fallback behaviorHandling CSS prefixs中所述 .

@supports

最近,CSS引入了自己的本机功能检测机制- @supports规则. 这与媒体查询的工作方式类似(另请参见" 响应式设计问题" )–除了不是根据分辨率,屏幕宽度或宽高比等媒体功能有选择地应用CSS,而是根据CSS功能是否有选择地有选择地应用CSS.支持的.

例如,我们可以重写前面的示例以使用@supports -请参见supports-feature-detect.htmlsupports-styling.css . 如果您看一下后者,则会看到几个@supports块,例如:

@supports (flex-flow: row) and (flex: 1) {

  main {
    display: flex;
  }

  main div {
    padding-right: 4%;
    flex: 1;
  }

  main div:last-child {
    padding-right: 0;
  }

}

仅当当前浏览器同时支持flex-flow: rowflex: 1声明时,此规则块才会在其中应用CSS规则. 为了使每个条件都起作用,您需要包括完整的声明(不仅仅是属性名称),并且最后不包括分号.

@supports也有ORNOT逻辑可用-如果flexbox属性不可用,则另一个块将采用float布局:

@supports not (flex-flow: row) and (flex: 1) {

  /* rules in here */

}

与上一个示例相比,这看起来更方便-我们可以在CSS中完成所有功能检测,而无需JavaScript,并且可以在单个CSS文件中处理所有逻辑,从而减少了HTTP请求. 这里的问题是浏览器支持@supports根本不支持@supports ,只有最新版本的Safari / iOS WebKit(9 + / 9.2 +)中才支持@supports ,而JavaScript版本应该可以在更旧的浏览器中使用(可能支持到IE8或9,尽管IE的较旧版本会出现其他问题,例如不支持Document.querySelector以及具有混乱的盒子模型).

JavaScript

We already saw an example of a JavaScript feature detection test earlier on. Generally, such tests are done via one of the following common patterns:

JavaScript特征检测技术摘要
特征检测类型 Explanation Example
如果成员对象 检查其父对象中是否存在某个方法或属性(通常是要检测到的使用API​​或其他功能的入口点).

if("geolocation" in navigator) { ... }

元素属性 使用Document.createElement()在内存中创建一个元素,然后检查其上是否存在属性. 所示示例是一种检测HTML5 Canvas支持的方法. function supports_canvas() {
return !!document.createElement('canvas').getContext;
}

if(supports_canvas()) { ... }
元素返回值的方法 使用Document.createElement()在内存中创建一个元素,然后检查其上是否存在方法. 如果是,请检查返回的值. See Dive Into HTML5 Video Formats detection test.
元素上的属性保留价值 使用Document.createElement()在内存中创建一个元素,将属性设置为某个值,然后检查该值是否保留. See Dive into HTML5 <input> types detection test.

注意 :上面示例中的双NOT!! )是一种强制返回值变为"适当"布尔值的方法,而不是可能会使结果偏斜的Truthy / Falsy值.

除了上面列出的功能," 深入HTML5检测HTML5功能"页面还提供了许多有用的功能检测测试,通常,您可以通过在自己喜欢的搜索中搜索"检测对YOUR-FEATURE-HERE的支持"来找到针对大多数事物的功能检测测试.搜索引擎. 请记住,虽然某些功能,但是,被称为是检测不到的-看到的Modernizr的名单Undetectables .

matchMedia

我们也想在这一点上提到Window.matchMedia JavaScript功能. 此属性使您可以在JavaScript中运行媒体查询测试. 看起来像这样:

if (window.matchMedia("(max-width: 480px)").matches) {
  // run JavaScript in here. 
}

举例来说,我们的Snapshot演示程序利用它来选择性地应用Brick JavaScript库并使用它来处理UI布局,但仅适用于小屏幕布局(宽度为480px或更小). 如果页面宽度小于或等于480px,我们首先使用media属性仅将Brick CSS应用于页面:

<link href="dist/brick.css" type="text/css" rel="stylesheet" media="all and (max-width: 480px)">

然后,我们在JavaScript中多次使用matchMedia() ,以仅在小屏幕布局上运行Brick导航功能(在较宽的屏幕布局中,所有内容均可一次看到,因此我们无需在不同视图之间导航).

if (window.matchMedia("(max-width: 480px)").matches) {
  deck.shuffleTo(1);  
}

Using Modernizr to implement feature detection

可以使用上面详述的技术来实现自己的特征检测测试. 但是,您最好使用专用的功能检测库,因为它使事情变得容易得多. 所有功能检测库的母亲都是Modernizr ,它可以检测几乎所有您需要的东西. 让我们看看现在如何使用它.

当您尝试使用Modernizr时,您不妨使用开发版本,其中包括所有可能的功能检测测试. 立即下载:

  1. 单击开发构建链接.
  2. 单击出现的页面上的粉红色的大构建按钮.
  3. 在出现的对话框中,单击顶部的下载链接.

将其保存在明智的位置,例如您在本文中为其他示例创建的目录.

在生产环境中使用Modernizr时,可以转到已经访问过的" 下载"页面 ,然后单击加号按钮仅显示需要功能检测到的功能. 然后,当您单击" 生成"按钮时,您将下载仅包含那些功能检测的自定义生成 ,从而大大减小了文件大小.

CSS

让我们看一下Modernizr在选择性应用CSS方面的工作方式.

  1. 首先,制作一份supports-feature-detect.htmlsupports-styling.css . 将它们另存为modernizr-css.htmlmodernizr-css.css .
  2. 更新HTML中的<link>元素,使其指向正确的CSS文件(还应将<title>元素更新为更合适的内容!):
      <link href =" modernizr-css.css" rel =" stylesheet"> 
  3. 在此<link>元素上方,添加一个<script>元素以将Modernizr库应用于页面,如下所示. 需要在可能使用该页面的任何CSS(或JavaScript)之前将其应用于页面.
      <script src =" modernizr-custom.js"> </ script> 
  4. 现在编辑您的开始<html>标记,使其如下所示:
      <html class =" no-js"> 

此时,尝试加载页面,您将了解Modernizr如何用于CSS功能. 如果查看浏览器开发人员工具的DOM检查器,您将看到Modernizr已更新您的<html> class值,如下所示:

<html class="js no-htmlimports sizes flash transferables applicationcache blobconstructor
blob-constructor cookies cors ...AND LOADS MORE VALUES!>

现在,它包含大量类别,这些类别指示不同技术功能的支持状态. 例如,如果浏览器根本不支持flexbox,则<html>的类名称为no-flexbox . 如果它确实支持现代的flexbox,它将获得flexbox的类名. 如果您在类列表中进行搜索,您还将看到与flexbox相关的其他内容,例如:

  • flexboxlegacy老Flexbox的规范(2009年).
  • flexboxtweener for 2011 in between syntax supported by IE10.
  • 用于flex-wrap属性的flexwrap ,在某些实现中不存在.

注意 :您可以找到所有类名称含义的列表-请参阅Modernizr检测到的功能 .

继续,让我们更新CSS以使用Modernizr而不是@supports . 进入modernizr-css.css ,并用以下内容替换两个@supports块:

/* Properties for browsers with modern flexbox */

.flexbox main {
  display: flex;
}

.flexbox main div {
  padding-right: 4%;
  flex: 1;
}

.flexbox main div:last-child {
  padding-right: 0;
}

/* Fallbacks for browsers that don't support modern flexbox */

.no-flexbox main div {
  width: 22%;
  float: left;
  padding-right: 4%;
}

.no-flexbox main div:last-child {
  padding-right: 0;
}

.no-flexbox footer {
  clear: left;
}

那么这是如何工作的呢? 因为所有这些类名都已放在<html>元素上,所以可以使用特定的后代选择器来定位支持或不支持功能的浏览器. 因此,这里我们仅将最上面的规则集应用于确实支持flexbox的浏览器,而最下面的规则集仅应用于不支持flexbox的浏览器( no-flexbox ).

注意 :请记住,这些类名也报告了Modernizr的所有HTML和JavaScript功能测试,因此,如果需要,您可以根据浏览器是否支持HTML或JavaScript功能来有选择地应用CSS.

注意 :如果您无法modernizr-css.html正常工作,请对照我们的modernizr-css.htmlmodernizr-css.css文件检查代码(另请参见此实时运行).

JavaScript

同样,Modernizr也为实现JavaScript功能检测做好了充分的准备. 它通过使全局Modernizr对象可用于其所应用的页面来实现此目的,该对象包含检测为true / false属性的功能的结果.

例如,在浏览器中加载我们的modernizr-css.html示例,然后尝试转到JavaScript控制台并输入Modernizr. 后面跟一些类名(这里也一样). 例如:

Modernizr.flexbox
Modernizr.websqldatabase
Modernizr.xhr2
Modernizr.fetch

The console will return true/false values to indicate whether your browser supports those features or not.

让我们看一个示例,以显示如何使用这些属性.

  1. 首先,对modernizr-js.html示例文件进行本地复制.
  2. 像我们之前的演示一样,使用<script>元素将Modernizr库附加到HTML. 将其放在现有的<script>元素上方,该元素会将Google Maps API附加到页面上.
  3. 接下来,使用有效的Google Maps API密钥在第二个<script>元素(现在)中填写YOUR-API-KEY占位符文本. 要获取密钥,请登录Google帐户,转到" 获取密钥/身份验证"页面,然后单击蓝色的" 获取密钥"按钮并按照说明进行操作.
  4. 最后,在HTML主体的底部(位于</body>标签之前)添加另一个<script>元素,并将以下脚本放入标签中:
      如果(Modernizr.geolocation){
    
       navigator.geolocation.getCurrentPosition(function(position){
    
         让latlng =新的google.maps.LatLng(position.coords.latitude,position.coords.longitude);
         让myOptions = {
           变焦:8
           中心:latlng,
           mapTypeId:google.maps.MapTypeId.TERRAIN,
           disableDefaultUI:true
         }
         let map = new google.maps.Map(document.getElementById(" map_canvas"),myOptions);
       });
    
     }其他{
       const para = document.createElement('p');
       para.textContent ='啊,没有地理位置!';
       document.body.appendChild(para);
     } 

试试你的例子! 在这里,我们使用Modernizr.geolocation测试来检查当前浏览器是否支持地理定位. 如果是这样,我们将运行一些代码来获取您设备的当前位置,并将其绘制在Google Map上.

Summary

本文详细介绍了功能检测,详细介绍了主要概念,并向您展示了如何实现自己的功能检测测试以及如何使用Modernizr库更轻松地实现测试.

接下来,我们将开始研究自动化测试.

In this module