码农备忘录当前使用的主题是Doubt,是我做的第八款主题,这款主题是在Way主题的基础上修改而来的。Doubt参考了很多国内优秀的极简主题,在主题介绍中将会感谢它们。主题设计时候已是作者进入社会打拼的第七年(2022.2)。当时也处在人生转折期。之所以取名为Doubt,是因为这些年来走了很多弯路,又到了岔路口,我第一次产生了对自己能力以及运气的怀疑。

主题Doubt

在此之前,本站使用的是Bye主题,其与Way是兄弟作品。由于自用,Bye主题并没有开源或出售。

bye主题wordpress

后期之所以弃用Way,是因为2020年底时候,新手站长站域名到期(xszzz.com),由于无心再折腾,决定放弃续费,将其更名为码农备忘录。而码农备忘录定位也只是发表我的一些开发文档或技术文章,所以后期为了突出文章,没有考虑做很多内链等SEO优化的东西,就采用了Way主题,但使用Way主题感觉有些和技术博客格格不入,才对其进行修改,这才有了Doubt主题。

当下我正准备开发END主题,但END主题我尚未进行设计,只是定位为突出文字。在突出文字的基础上,尽量简洁、大气、新颖,尽量多一些交互功能和内链。

我没有做过正常的UI设计,除却工作之外,我自己写东西的时候,一般是在开发中慢慢调整样式,以达到自己想要的效果,但这种粗略的开发模式难以做出优秀的作品。所以此次开发END主题之前我是打算先使用PS大概做一套设计,画一个粗略的图。当然PS我用的还是比较熟练吧,先前我曾使用PS为醉风云资源素材店(淘宝店)进行过全套设计。

但在设计之前,我打算先对本站主题Doubt进行重构,采用先前想好的技术进行实现。这样可以尽快熟悉代码,且在重构过程中,找到一些END主题的设计灵感。

WordPress主题END开发技术选型思路

重构Doubt我会尽量保持原主题的样式不做大的变动,可能只是改变一些小的样式,比如字体样式,文章文本的一些样式。


Doubt 现已重构完成(2024年9月3日),大家当前在本站看到的主题就是重构后的效果。当然部分样式和交互还在逐步微调中,主题具体重构的内容在后续的文章中将进行介绍。

在尝试LitSvelte框架之后,我更倾向于Svelte,其语法更友好。可使用Vite 进行初始化Svelte脚手架。

但测试之后,Svelte并不兼容IE11,但使用@vitejs/plugin-legacy 之后尝试可以兼容(暂时)。

由于此主题页面中,为突出文字内容及不改变原有SEO收录路径,所有有效内容(包括文章列表、文章内容、菜单、部分锚文本)还是采用PHP进行服务端渲染,所以,IE11中应该是会正常体现出这些内容。

当然页面样式可能需要使用CSS Hack或HTML Hack方式进行微调。

//只针对IE
@media screen\0 {
/*样式*/
}
<!--[if IE]> 所有的IE可识别 <![endif]-->

需要说明一下,我并无IE兼容的执念,虽然IE现在已经被微软放弃了,但由于在使用360极速浏览器时候,有一个极速模式和兼容模式,切换兼容模式时就是IE模式。这里有些同学的浏览器默认设置的是兼容模式,所以我是考虑这部分访客。

所以此次开发,我不会太考虑JS脚本的IE兼容性,毕竟现在没多少人用IE了。仅考虑其余低版本浏览器(如Chrome 60)。这里使用@vitejs/plugin-legacy 插件即可兼容,此插件会为低版本浏览器添加语法相对应的polyfill。

打包之后,polyfills文件可能会比较大,但编译结果是按需加载的,所以不用考虑加载效率。况且几百K之内的大小对于现在的网络和浏览器来说,完全不必担心。若想限制polyfills文件大小,可以配置仅需要的语法。

legacy({
      // 版本兼容(如原生ESM在低版本浏览器中不兼容问题)
      targets: [
        "defaults",
        "Chrome >= 52",
        "Edge >= 15",
        "Firefox >= 54",
        "Android >= 40",
        "Safari >= 10.1",
        "iOS >= 10.3",
        "IE >= 11",
      ],
      additionalLegacyPolyfills: ["regenerator-runtime/runtime"], // 面向IE11时需要用的插件
      renderLegacyChunks: true,
      polyfills: [] // 这里配置仅需要转义的语法
 })

由于Svelte 在运行时无虚拟Dom中间层的性能损耗,这里用起来也较Vue更轻量。

需要用到响应式渲染的内容并不太多。这里也仅用到框架的模板引擎功能。

使用框架而不直接用模板引擎的原因是:

  • 一些交互功能使用框架会便于开发,更便于后期主题的功能扩展
  • 便于使用ESM引入一些额外的包
  • 便于使用CSS预处理器(less/sass),这个很关键
  • 便于组件化以及组件复用,这个也很关键

当然,此次开发使用到Svelte可能会有很多坑,比如:

  • 多入口页打包配置
  • 打包结果呈现WordPress主题的php文件结构(这里是否需要手动拆分)

待我慢慢探索。


经过长期开发探索,由于wordpress需考虑到SEO,无法全部使用响应式数据填充页面,而必须使用PHP文件进行开发。再者,Svelte 是编译时框架,种种原因导致无法更好的进行开发环境预览,故放弃Svelte ,还是使用 Vue 。(2024-02-06)

最终方案:

  • 放弃Vite,使用原始开发方式
  • 编译和实时预览使用Node开发预览和编译程序
  • css预处理使用less,less是基于Javascript的,可以在node中使用其render函数进行程序化编译
  • 放弃TS,使用ES-Module方式开发JS
  • 有需要的模块如vue\axios等使用cdn链接引入
  • 放弃更好的兼容性,使用更新的js语法进行开发

醉风云博客十一周年了。

FengYun主题也即将完成它的使命。

深思熟虑之后,我决定,花费很长一段时间,为醉风云博客创作一款新的主题。

她将变得更加简单。她将更加突出文字。

终有一天,大象将重归原野。

...

原先是考虑使用Vue+Pjax架构打造一款极致主题的(很多博主这样搭配),但考虑到在这里只能用到Vue的模板引擎功能,且使用的频率和Vue的其他功能不会很多,有点大材小用,因为文章和大部分内容要兼顾SEO(PHP服务端渲染)。

若不考虑SEO,当然可以使用Vue+WordPress Rest API 开发单页应用来做一个网站。但是我的博客毕竟这么多年了,之前的文章也都被搜索引擎收录了,我不想改变这些收录的内容及路径。

另外也考虑到Vue的浏览器兼容性(个人博客还是兼容性好点吧),所以若使用Vue,就只能使用Vue2版(兼容IE11)使用CDN方式引入Vue浏览器版本,在Wordpress的主题开发相应的PHP代码中进行嵌入,开发形式不太友好,而且放弃了Webpack或Vite的编译,只能使用线上Babel进行运行时编译,效果不好。当然不编译的话,可以使用一些兼容性好的语法。

方案一

可能尝试使用Pjax+ArtTemplate(Art模板引擎)架构进行开发。Art模板引擎是我用的比较顺手的一款,它的性能很高(先前使用Handlebars/Ejs/Art搭配Koa搭建中间层服务,Art效率及语法更胜一筹),更加轻量(浏览器版本仅 6KB 大小)。

考虑到开发效率,还是尽量用一些新的语法,当然肯定要编译,这里,所有的页面我考虑开发时直接使用HTML静态页进行原始开发(使用MockJS造一些假数据),脚本使用TS,这样便于开发时的预览及编译。等开发完成,再手动将这些静态页其转为PHP(这里可以考虑使用数据化将mock数据和php占位符进行切换实现自动化编译)。

方案二

考虑到打包编译,为了实现热预览、组件化、响应式数据、自动化编译等功能,同时由于HTML原始开发貌似不太友好。为使用Vite打包工具,可能尝试采用Lit 或Svelte框架进行开发。

  • Lit是一个依据 Web-Component 构建的前端结构,同样轻量化(5 KB),简单易学,高效。
  • Svelte 在未使用虚拟DOM的情况下实现了响应式设计,其将更多操作放到编译阶段,解放运行阶段的脚本运行负担,其做法也类似于模板引擎。轻量化(编译后只有几 KB)。

我会尝试这两个框架,选出最优方案进行开发。

最终成型后,打算将其开源。

这款主题,我将其命名为「END」,是结束,也是开始。

届时,等主题完工,本站(码农备忘录)应该也会进行重构,尽情期待。

一、padStart()简介

JavaScript的字符串padStart()方法用于在当前字符串的开头添加指定数量的字符,以达到指定的字符串长度。如果当前字符串的长度大于或等于指定的字符串长度,则不会添加任何字符。

二、语法

string.padStart(targetLength [, padString])

三、参数解释

targetLength:要达到的字符串长度,必须为一个正整数。

padString:可选的填充字符串,如果不指定,则默认为一个空格。

四、使用实例

下面是一些使用padStart()方法的示例:

//小于两位补零
String(date.getMonth() + 1).padStart(2, '0');

//文件名生成
for (let i = 1; i <= 10; i++) {
    const fileName = `file${i.toString().padStart(2, '0')}.txt`;
    console.log(fileName); // 输出: file01.txt file02.txt file03.txt ... file10.txt
}

五、注意事项

1、如果fillString的长度大于targetLength,则会截取fillString的前面部分。

2、如果不指定fillString,则默认为一个空格。

3、如果当前字符串的长度大于或等于目标长度,则不会添加任何字符。

六、常用在哪里

padStart()方法常用于字符串的格式化,比如在输出表格时,确保每列的宽度相同,可以使用padStart()方法在开头添加空格或其他字符。还可以用于格式化日期、时间等。

七、关于padEnd

padEnd()可以在字符串的后面进行字符补全,语法参数等都和padStart()类似。

八、padEnd注意

如果补全字符串长度不足,则从左往右不断循环补全;如果长度超出可以补全的字符长度,则从左侧尽可能补全,补不到的没办法,只能忽略,例如'zhangxinxu'.padEnd(15, {})等同于执行'zhangxinxu'.padEnd(15, '[object Object]'),最多只能补5个字符,因此,只能补'[object Object]'前5个字符,于是最后结果是:'zhangxinxu[obje'

padString参数如果不设置,则会使用普通空格' '(U+0020)代替,也就是Space空格键敲出来的那个空格。

九、padEnd案例

在JS前端我们处理时间戳的时候单位都是ms毫秒,但是,后端同学返回的时间戳则不一样是毫秒,可能只有10位,以s秒为单位。所以,我们在前端处理这个时间戳的时候,保险起见,要先做一个13位的补全,保证单位是毫秒。使用示意:

timestamp = +String(timestamp).padEnd(13, '0');

十、兼容性

padStart()padEnd()是属于ES8(ES2017)的方法,兼容性:IE不支持,Chrome版本≥57

在OpenLayers中,根据多个经纬度坐标绘制多边形是一个常见的任务。你可以使用这些坐标来创建一个ol.geom.Polygon几何对象,然后将其添加到一个矢量图层中并在地图上显示。

以下是一个基本的步骤指南,教你如何在OpenLayers中根据多个经纬度坐标绘制多边形:

  1. 引入OpenLayers库
    确保你的HTML文件中已经引入了OpenLayers的JavaScript库。
  2. 创建地图容器
    在HTML中创建一个<div>元素作为地图的容器。
  3. 初始化地图
    使用OpenLayers的API来初始化地图,并设置视图。
  4. 创建多边形几何对象
    使用给定的经纬度坐标创建一个ol.geom.LinearRing(线性环,表示多边形的外环)和一个ol.geom.Polygon(多边形)几何对象。
  5. 创建矢量特征和图层
    将多边形几何对象添加到一个新的矢量特征中,并将该特征添加到一个矢量图层中。
  6. 将矢量图层添加到地图
    将矢量图层添加到地图中,以便在地图上显示多边形。

以下是一个具体的代码示例:

<!DOCTYPE html>  
<html lang="en">  
<head>  
  <meta charset="UTF-8">  
  <title>OpenLayers Polygon Example</title>  
  <link rel="stylesheet" href="https://openlayers.org/en/latest/css/ol.css" type="text/css">  
  <script src="https://openlayers.org/en/latest/build/ol.js"></script>  
  <style>  
    #map {  
      width: 100%;  
      height: 400px;  
    }  
  </style>  
</head>  
<body>  
  <div id="map"></div>  
  <script>  
    // 初始化地图  
    var map = new ol.Map({  
      target: 'map',  
      layers: [  
        new ol.layer.Tile({  
          source: new ol.source.OSM() // 使用OpenStreetMap作为地图源  
        })  
      ],  
      view: new ol.View({  
        center: ol.proj.fromLonLat([0, 0]), // 设置地图中心点  
        zoom: 2 // 设置地图缩放级别  
      })  
    });  
  
    // 定义多边形的经纬度坐标(注意:这些坐标需要是闭合的,即第一个和最后一个坐标应该是相同的)  
    var coordinates = [  
      [0, 0], // 第一个点  
      [10, 10], // 第二个点  
      [10, -10], // 第三个点  
      [0, 0] // 闭合多边形,回到第一个点  
    ];  
  
    // 将经纬度坐标转换为OpenLayers的内部坐标系统(如果需要的话,这里已经假设是WGS84经纬度)  
    // 注意:对于简单的经纬度坐标,如果地图视图已经设置为相应的投影(如EPSG:4326),则可能不需要转换。  
    // 但通常,我们会使用EPSG:3857(Web Mercator)作为地图的投影,因此这里可能需要转换,但在这个例子中我们省略了这一步,  
    // 因为我们直接使用了可以理解的经纬度坐标,并且OpenLayers在大多数情况下能够自动处理这些坐标。  
    // 如果你的坐标不是WGS84经纬度,或者你需要确保精确的坐标转换,请使用ol.proj.transform进行转换。  
  
    // 创建线性环(LinearRing)和多边形(Polygon)几何对象  
    var linearRing = new ol.geom.LinearRing(coordinates);  
    var polygon = new ol.geom.Polygon([linearRing]);  
  
    // 创建矢量特征,并将多边形几何对象设置为其几何属性  
    var feature = new ol.Feature(polygon);  
  
    // 创建矢量源,并将特征添加到源中  
    var vectorSource = new ol.source.Vector({  
      features: [feature]  
    });  
  
    // 创建矢量图层,并将源设置为其数据源  
    var vectorLayer = new ol.layer.Vector({  
      source: vectorSource,  
      style: new ol.style.Style({  
        fill: new ol.style.Fill({  
          color: 'rgba(255, 0, 0, 0.2)' // 设置填充颜色为红色透明  
        }),  
        stroke: new ol.style.Stroke({  
          color: '#ff0000', // 设置边框颜色为红色  
          width: 2 // 设置边框宽度  
        })  
      })  
    });  
  
    // 将矢量图层添加到地图中  
    map.addLayer(vectorLayer);  
  </script>  
</body>  
</html>

在这个例子中,我们创建了一个简单的红色多边形,它根据给定的经纬度坐标绘制在地图上。请注意,坐标数组是闭合的,即第一个和最后一个坐标是相同的,这是绘制多边形时的要求。如果你的坐标数组不是闭合的,OpenLayers可能无法正确识别它为多边形。

另外,请注意坐标系统的转换。在这个例子中,我们直接使用了经纬度坐标,并且假设地图视图已经设置为能够处理这些坐标。然而,在实际应用中,你可能需要将坐标从一种投影系统转换到另一种投影系统(例如,从WGS84经纬度转换到EPSG:3857 Web Mercator投影),这可以通过ol.proj.transform函数来实现。但在上面的例子中,我们省略了这一步,因为OpenLayers在大多数情况下能够自动处理这些坐标,并且我们使用了简单的经纬度坐标。

在OpenLayers中绘制多边形是一个常见的功能,它通常通过OpenLayers提供的绘制(Draw)交互来实现。以下是一个基本的步骤指南,教你如何在OpenLayers中绘制多边形:

1. 引入OpenLayers库

首先,你需要在HTML文件中引入OpenLayers库的JavaScript文件。你可以从OpenLayers的官方网站获取最新版本的链接。

<script src="https://openlayers.org/en/latest/build/ol.js"></script>

注意:这里的链接是示例用的,你应该使用OpenLayers提供的最新版本链接。

2. 创建地图容器

在HTML文件中创建一个用于显示地图的容器。这个容器将作为OpenLayers地图的视图窗口。

<div id="map" style="width: 100%; height: 400px;"></div>

3. 初始化地图

在JavaScript代码中初始化地图对象,并设置地图的中心点和缩放级别。

var map = new ol.Map({  
  target: 'map',  
  view: new ol.View({  
    center: ol.proj.fromLonLat([0, 0]), // 设置地图中心点  
    zoom: 2 // 设置地图缩放级别  
  })  
});

4. 添加绘制交互

创建一个绘制交互对象,并将其添加到地图中。这个交互对象将允许用户在地图上绘制多边形。

var draw = new ol.interaction.Draw({  
  source: new ol.source.Vector(),  
  type: 'Polygon' // 指定绘制类型为多边形  
});  
map.addInteraction(draw);

5. 监听绘制完成事件

监听绘制完成事件,并获取绘制的多边形几何对象。你可以在这个事件处理函数中执行一些额外的操作,比如保存多边形到数据库或进行空间分析。

draw.on('drawend', function(event) {  
  var feature = event.feature;  
  var geometry = feature.getGeometry(); // 获取绘制完成后的多边形几何对象  
  // 在这里可以对多边形进行进一步处理  
});

6. 样式设置(可选)

你可以为绘制的多边形设置样式,包括填充颜色、边框颜色、边框宽度等。

var styleFunction = function(feature) {  
  return new ol.style.Style({  
    fill: new ol.style.Fill({  
      color: 'rgba(255, 255, 255, 0.2)' // 设置填充颜色  
    }),  
    stroke: new ol.style.Stroke({  
      color: '#ffcc33', // 设置边框颜色  
      width: 2 // 设置边框宽度  
    }),  
    text: new ol.style.Text({  
      font: '12px Calibri,sans-serif',  
      text: '多边形',  
      fill: new ol.style.Fill({  
        color: '#000'  
      }),  
      stroke: new ol.style.Stroke({  
        color: '#fff',  
        width: 3  
      })  
    })  
  });  
};  
  
// 将样式应用到矢量图层上  
var vectorLayer = new ol.layer.Vector({  
  source: draw.getSource(),  
  style: styleFunction  
});  
map.addLayer(vectorLayer);

注意:在上面的代码中,我们创建了一个新的矢量图层,并将绘制交互的源(source)应用到这个图层上。同时,我们为这个图层设置了一个样式函数,用于定义多边形的样式。然而,在实际应用中,你可能不需要单独创建一个矢量图层来显示绘制的多边形,而是可以直接将绘制交互的源添加到已有的矢量图层上。

通过以上步骤,你就可以在OpenLayers中绘制多边形了。当然,OpenLayers的功能非常强大,你还可以进一步自定义绘制交互的行为和样式,以满足你的具体需求。

在 Vue.js 应用中,使用 Vue Router 进行路由管理时,常常需要在不同的路由之间传递参数。Vue Router 提供了几种方式来实现路由传参,包括通过 URL 路径参数、查询参数和命名视图。以下是一些常见的方法和示例:

1. 路径参数(Route Parameters)

路径参数通常用于传递具有唯一标识意义的参数,如用户 ID、文章 ID 等。

定义路由

// router/index.js  
import Vue from 'vue';  
import Router from 'vue-router';  
import User from '@/components/User.vue';  
import Post from '@/components/Post.vue';  
  
Vue.use(Router);  
  
export default new Router({  
  routes: [  
    {  
      path: '/user/:id', // 这里的 `:id` 是一个动态段  
      name: 'User',  
      component: User  
    },  
    {  
      path: '/post/:postId', // 这里的 `:postId` 是一个动态段  
      name: 'Post',  
      component: Post  
    }  
  ]  
});

访问路由并传递参数

// 在某个组件中  
this.$router.push({ name: 'User', params: { id: 123 } });  
this.$router.push({ name: 'Post', params: { postId: 456 } });

在组件中获取参数

// User.vue  
<template>  
  <div>User ID: {{ $route.params.id }}</div>  
</template>  
  
<script>  
export default {  
  computed: {  
    userId() {  
      return this.$route.params.id;  
    }  
  }  
};  
</script>  
  
// Post.vue  
<template>  
  <div>Post ID: {{ $route.params.postId }}</div>  
</template>  
  
<script>  
export default {  
  computed: {  
    postId() {  
      return this.$route.params.postId;  
    }  
  }  
};  
</script>

2. 查询参数(Query Parameters)

查询参数通常用于传递非唯一标识意义的参数,如搜索条件、分页信息等。

定义路由(不需要特殊定义)

// router/index.js  
import Vue from 'vue';  
import Router from 'vue-router';  
import SearchResults from '@/components/SearchResults.vue';  
  
Vue.use(Router);  
  
export default new Router({  
  routes: [  
    {  
      path: '/search',  
      name: 'SearchResults',  
      component: SearchResults  
    }  
  ]  
});

访问路由并传递参数

// 在某个组件中  
this.$router.push({ name: 'SearchResults', query: { q: 'vue', page: 2 } });

在组件中获取参数

// SearchResults.vue  
<template>  
  <div>  
    <p>Search Query: {{ $route.query.q }}</p>  
    <p>Page: {{ $route.query.page }}</p>  
  </div>  
</template>  
  
<script>  
export default {  
  computed: {  
    searchQuery() {  
      return this.$route.query.q;  
    },  
    page() {  
      return this.$route.query.page;  
    }  
  }  
};  
</script>

3. 编程式导航中的 props 传参

Vue Router 还支持将路由参数作为 props 传递给组件,这样可以使组件更加解耦和可复用。

定义路由时使用 props: true

// router/index.js  
import Vue from 'vue';  
import Router from 'vue-router';  
import User from '@/components/User.vue';  
  
Vue.use(Router);  
  
export default new Router({  
  routes: [  
    {  
      path: '/user/:id',  
      name: 'User',  
      component: User,  
      props: true // 这样可以将参数作为 props 传递给 User 组件  
    }  
  ]  
});

在组件中接收 props

// User.vue  
<template>  
  <div>User ID: {{ id }}</div>  
</template>  
  
<script>  
export default {  
  props: ['id']  
};  
</script>

使用 props 传递参数时,不需要通过 $route 对象来访问参数,直接通过 props 接收即可。

总结

  • 路径参数:适用于传递具有唯一标识意义的参数,如用户 ID。
  • 查询参数:适用于传递非唯一标识意义的参数,如搜索条件。
  • 编程式导航中的 props:使组件更加解耦和可复用。

通过这些方法,你可以在 Vue.js 应用中灵活地传递和使用路由参数。

前段时间在面试的时候,被问到原子类CSS,鉴于自己这个老前端已经几年没有关注前端最新的技术了,对于“原子类”这一名词有些困扰。

事后一查,这不就是“bootstrap”的样式类的新名词嘛?老旧的东西又拿出来说。其实这都是最原始的CSS设计模式了了。

原子类CSS(Atomic CSS)是一种CSS设计模式,它将样式属性拆分为独立的、具有特定用途的类。每个类通常只包含一个样式属性,或者是一组紧密相关的样式属性的组合。通过将这些类组合在一起,可以快速构建出复杂的样式。以下是对原子类CSS的详细解析:

一、原子类CSS的核心原则

  1. 原子化:将样式分解为最小的可重用单元,即“原子”。这些原子通常是单个像素或极其微小的变化,例如颜色、大小、位置等。
  2. 可重用性:每个原子类都是独立且可重用的,可以在不同的元素和场景中重复使用。
  3. 组合性:通过组合不同的原子类,可以构建出复杂的样式和布局,而无需编写大量的定制CSS。

二、原子类CSS的优势

  1. 提高开发效率:由于原子类CSS提供了大量的预定义类,开发人员可以快速应用样式,而无需从头编写CSS代码。
  2. 减少代码冗余:通过重用原子类,可以避免在多个地方重复相同的样式代码,从而减少代码冗余。
  3. 易于维护:由于每个原子类只负责一个或少数几个样式属性,因此代码更加清晰和易于理解。当需要修改样式时,只需调整相应的原子类即可。
  4. 增强可定制性:开发人员可以根据自己的需求添加自定义的原子类,或者根据项目的需求修改现有的原子类。

三、原子类CSS的实现方式

  1. 工具与库:原子类CSS可以通过工具如Tachyons、Tailwind CSS等实现。这些工具提供了一套预定义的原子类,可以快速地应用于HTML元素。
  2. 命名约定:原子类CSS通常使用功能性的命名约定,如.mr1(外边距右侧1个单位)、.bg-red(背景颜色为红色)等。这些命名直观且易于理解。
  3. 与前端框架的集成:原子类CSS可以与各种前端框架(如Angular、React、Vue等)集成,并提供大量的插件和扩展选项。

四、原子类CSS的应用场景

  1. 快速开发:在需要快速构建Web应用程序或原型时,原子类CSS可以大大提高开发效率。
  2. 团队协作:在团队协作中,使用原子类CSS可以减少代码冲突和不一致性,因为每个开发人员都可以使用相同的预定义类来构建样式。
  3. 定制化需求:当项目需要高度定制化时,原子类CSS提供了足够的灵活性和可定制性来满足这些需求。

五、示例

以下是一个使用Tailwind CSS(一种流行的原子类CSS框架)的示例:

<div class="bg-blue-500 text-white p-4">  
  Hello Tailwind CSS!  
</div>

在这个示例中,bg-blue-500 类设置了背景颜色为蓝色,text-white 类设置了文本颜色为白色,p-4 类设置了内边距为4个单位。通过组合这些原子类,我们可以快速构建出具有特定样式的HTML元素。

综上所述,原子类CSS是一种灵活且强大的CSS设计模式,它可以帮助简化样式管理并提高代码的可维护性。通过使用原子类CSS框架(如Tailwind CSS),开发人员可以更加高效地构建和维护Web应用程序的界面。

CSS变量简介

CSS变量的定义及使用如下,可定义的类型非常广泛。

/* 声明 */
--VAR_NAME: <声明值>;
/* 使用 */
var(--VAR_NAME)

/* 根元素选择器(全局作用域),例如 <html> */
:root {
  /* CSS 变量声明 */
  --main-color: #ff00ff;
  --main-bg: rgb(200, 255, 255);
  --logo-border-color: rebeccapurple;

  --header-height: 68px;
  --content-padding: 10px 20px;

  --base-line-height: 1.428571429;
  --transition-duration: .35s;
  --external-link: "external link";
  --margin-top: calc(2vh + 20px);
}

body {
  /* 使用变量 */
  color: var(--main-color);
}

与 SASS、LESS预处理器变量的编译时处理不同,CSS 变量由浏览器在运行时处理,这使得它们更加强大和灵活。

CSS 到 JS

在 CSS 变量出现之前,将值从 CSS 传递到 JS 非常困难,甚至需要一些 hack 技巧。现在有了 CSS 变量,可以直接通过 JS 访问变量值并进行修改。

// 定义 CSS 变量
.breakpoints-data {
  --phone: 480px;
  --tablet: 800px;
}
const breakpointsData = document.querySelector('.breakpoints-data');

// 获取 CSS 变量的值
const phone = getComputedStyle(breakpointsData)
    .getPropertyValue('--phone');

// 设置 CSS 变量的新值
breakpointsData.style
    .setProperty('--phone', 'custom');

本站重构后,也合理的利用了CSS变量,但方式更加灵活。

比如本站的主题切换功能,白天与夜晚主题。在根节点root中使用多个变量组,使用不同的元素选择器进行分组。然后通过js更改元素的class类或者属性对主题进行切换。

:root {
    --wp--preset--font-size--normal: 16px;
    --wp--preset--font-size--huge: 42px;
}
:root {
    --my-preset-line-height-1: 1.8;
    --my-preset-letter-spacing-1: .04em;
    --my-preset-color-font-1: #444444;
    --my-preset-color-font-2: #333333;
    --my-preset-color-font-3: #000000;
    --my-preset-color-bg-1: #ffffff;
    --my-preset-color-bg-2: #fbfbfb;
    --my-preset-color-bg-3: #1f1e2c;
    --my-preset-color-alert-bg: #ffffff;
    --my-preset-color-button-bg-1: #ffffff;
    --my-preset-color-button-font-1: #f96b4d;
    --my-preset-color-button-bg-active: #f96b4d;
    --my-preset-color-active: #f96b4d;
    --my-preset-color-active-hover: #f5ad6e;
    --my-preset-color-font-gray-1: #9f9f9f;
    --my-preset-color-font-gray-2: #cdcdcd;
    --my-preset-color-font-gray-3: #888888;
    --my-preset-color-border-1: #d8d8d8;
    --my-preset-color-border-2: #cccccc;
    --my-preset-color-border-3: #eeeeee;
    --my-preset-color-bg-gray-1: #f8f8f8;
    --my-preset-color-bg-gray-2: #fbfbfb;
    --my-font-family-1: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Hiragino Sans GB, Microsoft YaHei UI, Microsoft YaHei, Source Han Sans CN, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
    --my-font-family-2: NotoSerifSC, "Microsoft Yahei", 宋体, sans-serif;
    --my-font-family-3: 'Helvetica', 'Microsoft Yahei', '冬青黑体简体中文 w3', 微软雅黑, 'Tahoma', 'Arial', 'SimSun';
    --my-preset-color-scrollbar-thumb: #d2d2d2;
    --my-preset-color-scrollbar-track: rgba(51, 51, 51, 0.1);
}
[data-user-color-scheme='dark'] {
    --my-preset-color-bg-1: #1f1e2c;
    --my-preset-color-bg-2: #1a1927;
    --my-preset-color-bg-3: #0b0b10;
    --my-preset-color-alert-bg: #262631;
    --my-preset-color-font-1: #d5d5d5;
    --my-preset-color-font-2: #f5f5f5;
    --my-preset-color-font-3: #ffffff;
    --my-preset-color-border-3: #3f3d55;
    --my-preset-color-button-bg-1: #f96b4d;
    --my-preset-color-button-bg-active: #f5ad6e;
    --my-preset-color-button-font-1: #f5f5f5;
    --my-preset-color-bg-gray-1: #2e2d3b;
    --my-preset-color-font-gray-3: #a9a9a9;
    --my-preset-color-border-2: #aaaaaa;
    --my-preset-color-scrollbar-thumb: #3b3b5f;
    --my-preset-color-scrollbar-track: #1f1e2c;
}
export default {
  template,
  setup() {
    const colorScheme = ref(window.localStorage.getItem('user-color-scheme') || "light");
    const setColorScheme = (scheme) => {
      colorScheme.value = scheme;
      document.documentElement.setAttribute('data-user-color-scheme', scheme);
      window.localStorage.setItem('user-color-scheme', scheme);
    };

    return { colorScheme, setColorScheme };
  },
};

除此之外还有很多 css 原生能力,比如:Mixins、运算符等。

更改.gitignore后,需要通知Git重新读取.gitignore文件。可以使用以下命令来刷新Git的索引,以应用新的忽略规则:

git rm -r --cached .
git add .
git commit -m "Refresh .gitignore"

这些命令的作用是:

  1. git rm -r --cached .从索引中移除所有文件和目录,但保留在本地磁盘上。--cached选项指定只移除索引中的跟踪,而不删除实际文件。
  2. git add .重新添加所有文件和目录到索引中,此时Git会重新读取.gitignore文件来应用规则。
  3. git commit -m "Refresh .gitignore"提交更新后的索引变化。

请注意,git rm -r --cached .会移除所有文件和目录的跟踪,这意味着它会移除所有文件的版本历史,只保留文件内容。如果你只想刷新.gitignore规则对部分文件和目录的应用,你可以单独指定这些文件和目录。

前端的单元测试包括但不限于:单元功能测试、UI 测试、兼容性测试等等。一个测试体系大体包括四部分:

本文会通过一个例子,来一步步了解如何进行前端单元测试。

本文举的例子中,没有涉及测试运行器,只涉及测试框架、断言库和测试覆盖率。并以 Mocha + Should + Istanbul 组合为例。

新建项目

如果你的电脑上没有安装 Node.js,那么你需要访问它的官网,下载并安装到你的电脑上。NPM 是 Node.js 的包管理工具,会随着 Node.js 一起安装。

然后,我们需要用 NPM(Node Package Manager)来管理依赖包,所以先初始化 NPM 的配置文件 package.json,执行指令:

$ npm init -y

-y 参数表示不进行询问,直接使用默认的配置。

下面我们在 src 目录下,新建 main.js 文件,并编写一个 factorial 函数(用于求数的阶乘):

// main.js

var factorial = function(n) {
  if (n === 0) {
    return 1;
  }

  return factorial(n - 1) * n;
};

if (require.main === module) {
  // 如果是在命令行中执行 main.js,则此处会执行。
  // 如果 main.js 被其他文件 require,则此处不会执行。
  var n = Number(process.argv[2]);
  console.log('factorial(' + n + ') is', factorial(n));
}

运行一下这个文件,看看结果是否正确。执行指令:node ./src/main.js 5,效果如下:

结果是 120,符合预期。但是一个例子并不能说明什么,我们还需要对负数、非数字、小数、很大的数等进行验证,在逐步的验证过程中,代码中的不足也会逐渐暴露出来。所以接下来我们将进行测试驱动开发(Test-Driven Development, TDD),通过不断的测试来完善代码。

编写测试文件

首先,在 main.js 文件最后添加代码:

exports.factorial = factorial;

这段代码的作用是将 factorial 函数暴露出去,这样才可以在其他文件中 require 这个函数。

通常,测试文件与所要测试的源文件同名,但是后缀名为 .test.js(表示测试)或 .spec.js(表示规格)。例如,main.js 的测试文件就是 main.test.js

// main.test.js

var main = require('../src/main');
var should = require('should');

describe('test/main.js', function() {
  it('should equal 1 when n === 0', function() {
    should(main.factorial(0)).equal(1);
  });
});

上面的代码中:

  • describe 块称为“测试套件(test suite)”,表示一组相关的测试。它是一个函数,第一个参数是测试套件的名称,第二个参数是一个实际执行的函数。
  • it 块称为“测试用例(test case)”,表示一个单独的测试,是测试的最小单位。它也是一个函数,第一个参数是测试用例的名称,第二个参数是一个实际执行的函数。

一个测试文件中,可以包含一个或多个 describe 块,一个 describe 块中可以包含一个或多个 it 块。

想要运行这个测试文件,需要安装依赖 Mocha 和 Should:

$ npm install --save-dev mocha should

然后,在 package.json 中新建一条 NPM 指令:

"scripts": { "test": "./node_modules/.bin/mocha ./test/main.test.js" } 

该指令的作用就是:使用安装在项目目录中的 Mocha 命令 ./node_modules/.bin/mocha 来测试 ./test/main.test.js 文件。

执行这个指令 npm run test,结果如下(可以看到测试通过):

到这里,我们就使用测试框架 + 断言库,体验了基本的单元测试流程,接下来我们通过不断完善测试用例,来使代码健壮起来。

完善测试用例

首先,明确函数功能。我们的 factorial 函数应该有以下功能:

  • 当 n === 0 时,返回 1
  • 当 n > 0 时,返回 factorial(n - 1) * n
  • 当 n < 0 时,抛出错误,因为没有意义。
  • 当 n 不是数字时,抛出错误。
  • 当 n > 10 时,抛出错误(本文为了演示,只进行 10 以内的阶乘运算)。

然后,我们根据确定好的功能来完善测试用例:

var main = require('../src/main');
var should = require('should');

describe('test/main.js', function() {
  it('should equal 1 when n === 0', function() {
    should(main.factorial(0)).equal(1);
  });

  it('should equal 1 when n === 1', function() {
    should(main.factorial(1)).equal(1);
  });

  it('should equal 3628800 when n === 10', function() {
    should(main.factorial(10)).equal(3628800);
  });

  it('should throw when n > 10', function() {
    (function() {
      main.factorial(11);
    }.should.throw('n should <= 10'));
  });

  it('should throw when n < 0', function() {
    (function() {
      main.factorial(-1);
    }.should.throw('n should >= 0'));
  });

  it('should throw when n is not Number', function() {
    (function() {
      main.factorial('123');
    }.should.throw('n should be a Number'));
  });
});

执行测试指令 npm run test,效果如下:

可以看到后面三个测试用例都没有通过,这说明 factorial 函数并不是在所有情况下都可以正常运行,所以我们需要更新 factorial 的实现:

var factorial = function(n) {
  if (typeof n !== 'number') {
    throw new Error('n should be a Number');
  }

  if (n < 0) {
    throw new Error('n should >= 0');
  }

  if (n > 10) {
    throw new Error('n should <= 10');
  }

  if (n === 0) {
    return 1;
  }

  return factorial(n - 1) * n;
};

再次执行测试指令 npm run test,效果如下:

可以看到,所有的测试用例都通过了,这证明 factorial 函数的功能已经符合了我们的预期要求,而且代码健壮性有了很大的提高。

以上就是 TDD 的基本流程,总的来说就是:首先明确程序的功能,然后跑测试用例,如果测试用例没有通过,修改程序,直到测试用例通过

生成覆盖率

如果你想知道测试用例是否合理,可以用“代码覆盖率”来判断。一般而言,如果测试用例写的合理,那么代码覆盖率越高越好,但不是绝对的。

代码覆盖率包括以下几个方面:

  • 行覆盖率:是否每一行都执行了
  • 函数覆盖率:是否每个函数都调用了
  • 分支覆盖率:是否每个 if 代码块都执行了
  • 语句覆盖率:是否每个语句都执行了

生成代码覆盖率,需要用到插件 Istanbul,首先将其安装:

$ npm install --save-dev istanbul

然后,在 package.json 中新建一条 NPM 指令,用于生成覆盖率:

"scripts": { "coverage": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha" }

注意,指令中 _mocha 的下划线不能省略。因为,mocha 和 _mocha 是两个不同的命令,前者会新建一个进程执行测试,而后者是在当前进程(即 Istanbul 所在的进程)执行测试,只有这样, Istanbul 才会捕捉到覆盖率数据。其他测试框架也是如此,必须在同一个进程执行测试。

执行这个指令 npm run coverage,结果如下:

将 coverage/lcov-report 目录下的 index.html 文件在浏览器中运行,可以查看具体的覆盖率。如图所示:

其实,这次的覆盖率应该是 100%,因为函数在被其他文件引用时 24、25 这两行不会执行,所以没法测。由于这两行代码仅仅是为了刚开始方便演示用,之后我们就不在命令行中测试了,所以直接将这两行语句所在的 if 块删除即可。

再次执行测试指令,就得到了 100% 的覆盖率:

上传覆盖率

想要展示测试覆盖率,有两个网站可供选择:Codecov 和 Coveralls。本文以 Codecov 为例。

首先,打开 Codecov 官网,绑定 Github 账号之后,选择要展示测试覆盖率的仓库。

然后,安装 Codecov:

$ npm install --save-dev codecov

接着,在 package.json 中新建一条 NPM 指令,来上传测试覆盖率:

"script": { "codecov": "cat ./coverage/lcov.info | ./node_modules/.bin/codecov" }

其中 cat ./coverage/lcov.info 用于读取 coverage 目录下的 lcov.info 文件,./node_modules/.bin/codecov 用于将覆盖率上传到 Codecov 网站。

该指令在接下来配置 CI(Continuous integration, 持续集成)时会用到。

持续集成

如果每次修改代码之后,都手动进行单元测试,不仅加重工作量,而且容易出错,因此我们需要进行自动化测试,这就用到了持续集成。

持续集成是一种软件开发实践,每次集成都通过自动化的构建(包括编译,发布,测试等)来验证,从而尽早地发现代码中的错误。

可供选择的持续集成工具有 Travis CI 和 Circle CI。本文以 Travis CI 为例。

使用 Travis CI

首先,Travis CI 进入官网后,点击 Sign In 按钮绑定 Github。然后在仓库列表中选择你要进行持续集成的仓库,点击按钮启用:

然后,你需要在项目根目录下创建 .travis.yml 文件(如果没有这个文件,Travis CI 会默认执行 npm install 和 npm test),配置文件示例如下:

# 要使用的语言
language: node_js

# 要使用的语言版本
node_js:
  - 10

# 缓存 NPM 依赖,加快构建
cache:
  directories:
    - node_modules

# 安装依赖
install:
  - npm install

# 执行指令
script:
  - npm run coverage

# 指令执行成功后
after_success:
  - npm run codecov

# 指定分支
branches:
  only:
    - master

最后,将所有修改提交到远程仓库的 master 分支上,就可以看到 Travis CI 正在自动构建。

展示徽章

当 CI 构建完成之后,我们可以通过访问 Travis CI 和 Codecov 的网站查看到详细结果,当然也可以将结果以徽章的形式放入 README,这样更清晰明了。

Travis CI 的徽章这样获取:

Codecov 的徽章这样获取:

每当 CI 构建完成,结果就会以徽章的形式,展示在你的项目文档中。


参考资料: