Monday, November 22, 2021

SegmentFault 最新的文章

SegmentFault 最新的文章

SegmentFault 最新的文章


CSS mask 实现鼠标跟随镂空效果

Posted: 21 Nov 2021 06:55 PM PST

偶然在某思看到这样一个问题,如何使一个div的部分区域变透明而其他部分模糊掉?,最后实现效果是这样的

237330258-6181fcdb471cf

进一步,还能实现任意形状的镂空效果

Kapture 2021-11-20 at 13.44.26

鼠标经过的地方清晰可见,其他地方则是模糊的。

可能一开始无从下手,不要急,可以先从简单的、类似的效果开始,一步一步尝试,一起看看吧。

一、普通半透明的效果

比如平时开发中碰到更多的可能是一个半透明的效果,有点类似于探照灯(鼠标外面的地方是半透明遮罩,看起来会暗一点)。如下:

image-20211117200548416

那先从这种效果开始吧,假设有这样一个布局:

<div class="wrap" id="img">     <img class="prew" src="https://tva1.sinaimg.cn/large/008i3skNgy1gubr2sbyqdj60xa0m6tey02.jpg"> </div>

那么如何绘制一个镂空的圆呢?先介绍一种方法

其实很简单,只需要一个足够大的投影就可以了,原理如下

image-20211117195737723

这里可以用伪元素::before来绘制,结构更加精简。用代码实现就是

.wrap::before{   content:'';   position: absolute;   width: 100px;   height: 100px;   border-radius: 50%;   left: 50%;   top: 50%;   transform: translate(-50%,-50%); /*默认居中*/   box-shadow: 0 0 0 999vw rgba(0, 0, 0, .5); /*足够大的投影*/ }

可以得到这样的效果

image-20211117200548416

二、借助 CSS 变量传递鼠标位置

按照以往的经验,可能会在 js 中直接修改元素的 style 属性,类似这样

img.addEventListener('mousemove', (ev) => {     img.style.left = '...';     img.style.top = '...'; })

但是这样交互与业务逻辑混杂在一起,不利于后期维护。其实,我们只需要鼠标的坐标,在 CSS 中也能完全实现跟随的效果。

这里借助 CSS 变量,那一切就好办了!假设鼠标的坐标是 [--x,--y](范围是[0, 1]),那么遮罩的坐标就可以使用 calc计算了

.wrap::before{   left: calc(var(--x) * 100%);   top: calc(var(--y) * 100%); }

然后鼠标坐标的获取可以使用 JS 来计算,也比较容易,如下

img.addEventListener('mousemove', (ev) => {     img.style.setProperty('--x', ev.offsetX / ev.target.offsetWidth);     img.style.setProperty('--y', ev.offsetY / ev.target.offsetHeight); })

这样,半透明效果的镂空效果就完成了

Kapture 2021-11-17 at 20.26.27

完整代码可以访问: backdrop-shadow (codepen.io)

三、渐变也能实现半透明的效果

除了上述阴影扩展的方式,CSS 径向渐变也能实现这样的效果

绘制一个从透明到半透明的渐变,如下

.wrap::before{     content: '';     position: absolute;     width: 100%;     height: 100%;     left: 0;     top: 0;     background: radial-gradient( circle at center, transparent 50px, rgba(0,0,0,.5) 51px); }

可以得到这样的效果

image-20211117200548416

然后,把鼠标坐标映射上去就可以了。从这里就可以看出 CSS 变量的好处,无需修改 JS,只需要在CSS中修改渐变中心点的位置就可以实现了

.wrap::before{   background: radial-gradient( circle at calc(var(--x) * 100% )  calc(var(--y) * 100% ), transparent 50px, rgba(0,0,0,.5) 51px); }

Kapture 2021-11-18 at 19.51.30

四、背景模糊的效果尝试

CSS 中有一个专门针对背景(元素后面区域)的属性:backdrop-filter。使用方式和 filter完全一致!

backdrop-filter: blur(10px);

下面是 MDN 中的一个示意效果

image-20211119191341911

backdrop-filter是让当前元素所在区域后面的内容模糊,要想看到效果,需要元素本身半透明或者完全透明;而filter是让当前元素自身模糊。有兴趣的可以查看这篇文章: CSS backdrop-filter简介与苹果iOS毛玻璃效果 « 张鑫旭-鑫空间-鑫生活 (zhangxinxu.com)

需要注意的是,这种模糊与背景的半透明度没有任何关系,哪怕元素本身是透明的,仍然会有效果。例如下面是去除背景后的效果 ,整块都是模糊的

image-20211119193956128

如果直接运用到上面的例子会怎么样呢?

1. 阴影实现

在上面第一个例子中添加 backdrop-filter

.wrap::before{   content:'';   position: absolute;   width: 100px;   height: 100px;   border-radius: 50%;   left: 50%;   top: 50%;   transform: translate(-50%,-50%); /*默认居中*/   box-shadow: 0 0 0 999vw rgba(0, 0, 0, .5); /*足够大的投影*/   backdrop-filter: blur(5px) }

得到效果如下

Kapture 2021-11-19 at 19.20.57

可以看到圆形区域是模糊的,正好和希望的效果相反。其实也好理解,只有圆形区域才是真实的结构,外面都是阴影,所以最后作用的范围也只有圆形部分

2. 渐变实现

现在在第二个例子中添加 backdrop-filter

.wrap::before{     content: '';     position: absolute;     width: 100%;     height: 100%;     left: 0;     top: 0;     background: radial-gradient( circle at calc(var(--x) * 100% )  calc(var(--y) * 100% ), transparent 50px, rgba(0,0,0,.5) 51px);       backdrop-filter: blur(5px) }

效果如下

Kapture 2021-11-19 at 19.31.22

已经全部都模糊了,只是圆形区域外暗一些。由于::before的尺寸占据整个容器,所以整个背后都变模糊了,圆形外部比较暗是因为半透明渐变的影响。

总之还是不能满足我们的需求,需要寻求新的解决方式。

五、CSS MASK 实现镂空

与其说是让圆形区域不模糊,还不如说是把那块区域给镂空了。就好比之前是一整块磨砂玻璃,然后通过 CSS MASK 打了一个圆孔,这样透过圆孔看到后面肯定是清晰的。

可以对第二个例子稍作修改,通过径向渐变绘制一个透明圆,剩余部分都是纯色的遮罩层,示意如下

image-20211120113029155

用代码实现就是

.wrap::before{     content: '';     position: absolute;     width: 100%;     height: 100%;     left: 0;     top: 0;     -webkit-mask: radial-gradient( circle at calc(var(--x, .5) * 100% )  calc(var(--y, .5) * 100% ), transparent 50px, #000 51px);       background: rgba(0,0,0,.3);       backdrop-filter: blur(5px) }

这样就实现了文章开头的效果

237330258-6181fcdb471cf

完整代码可以查看:backdrop-mask (codepen.io)

六、CSS MASK COMPOSITE 实现更丰富的镂空效果

除了使用径向渐变绘制遮罩层以外,还可以通过 CSS MASK COMPOSITE(遮罩合成)的方式来实现。标准关键值如下(firefox支持):

/* Keyword values */ mask-composite: add; /* 叠加(默认) */ mask-composite: subtract; /* 减去,排除掉上层的区域 */ mask-composite: intersect; /* 相交,只显示重合的地方 */ mask-composite: exclude; /* 排除,只显示不重合的地方 */

遮罩合成是什么意思呢?可以类比 photoshop 中的形状合成,几乎是一一对应的

image-20211120123004278

-webkit-mask-composite 与标准下的值有所不同,属性值非常多,如下(chorme 、safari 支持)

-webkit-mask-composite: clear; /*清除,不显示任何遮罩*/ -webkit-mask-composite: copy; /*只显示上方遮罩,不显示下方遮罩*/ -webkit-mask-composite: source-over;  -webkit-mask-composite: source-in; /*只显示重合的地方*/ -webkit-mask-composite: source-out; /*只显示上方遮罩,重合的地方不显示*/ -webkit-mask-composite: source-atop; -webkit-mask-composite: destination-over; -webkit-mask-composite: destination-in; /*只显示重合的地方*/ -webkit-mask-composite: destination-out;/*只显示下方遮罩,重合的地方不显示*/ -webkit-mask-composite: destination-atop; -webkit-mask-composite: xor; /*只显示不重合的地方*/

是不是一脸懵?这里做了一个对应的效果图,如果不太熟练,使用的时候知道有这样一个功能,然后对着找就行了

image-20211120130421281

回到这里,可以绘制一整块背景和一个圆形背景,然后通过遮罩合成排除(mask-composite: exclude)打一个孔就行了,实现如下

.wrap::before{     content: '';     position: absolute;     width: 100%;     height: 100%;     left: 0;     top: 0;     -webkit-mask: url("data:image/svg+xml,%3Csvg width='50' height='50' viewBox='0 0 50 50' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='25' cy='25' r='25' fill='%23C4C4C4'/%3E%3C/svg%3E"), linear-gradient(red, red);       -webkit-mask-size: 50px, 100%;       -webkit-mask-repeat: no-repeat;       -webkit-mask-position: calc(var(--x, .5) * 100% + var(--x, .5) * 100px - 50px )  calc(var(--y, .5) * 100% + var(--y, .5) * 100px - 50px ), 0;       -webkit-mask-composite: xor;   /*只显示不重合的地方, chorem 、safari 支持*/       mask-composite: exclude; /* 排除,只显示不重合的地方, firefox 支持 */       background: rgba(0,0,0,.3);       backdrop-filter: blur(5px) }

需要注意-webkit-mask-position中的计算,这样也能很好的实现这个效果

237330258-6181fcdb471cf

完整代码可以查看:backdrop-mask-composite (codepen.io)

你可能已经发现,上述例子中的圆是通过 svg 绘制的,还用到了遮罩合成,看着好像更加繁琐了。其实呢,这是一种更加万能的解决方式,可以带来无限的可能性。比如我需要一个星星⭐️的镂空效果,很简单,先通过一个绘制软件画一个

image-20211120131056453

然后把这段 svg 代码转义一下,这里推荐使用张鑫旭老师的SVG在线压缩合并工具

image-20211120131335734

替换到刚才的例子中就可以了

.wrap::before{     content: '';     position: absolute;     width: 100%;     height: 100%;     left: 0;     top: 0;     -webkit-mask: url("data:image/svg+xml,%3Csvg width='96' height='91' viewBox='0 0 96 91' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M48 0l11.226 34.55h36.327l-29.39 21.352L77.39 90.45 48 69.098 18.61 90.451 29.837 55.9.447 34.55h36.327L48 0z' fill='%23C4C4C4'/%3E%3C/svg%3E"), linear-gradient(red, red);       -webkit-mask-size: 50px, 100%;       -webkit-mask-repeat: no-repeat;       -webkit-mask-position: calc(var(--x, .5) * 100% + var(--x, .5) * 100px - 50px )  calc(var(--y, .5) * 100% + var(--y, .5) * 100px - 50px ), 0;       -webkit-mask-composite: xor;   /*只显示不重合的地方, chorem 、safari 支持*/       mask-composite: exclude; /* 排除,只显示不重合的地方, firefox 支持 */       background: rgba(0,0,0,.3);       backdrop-filter: blur(5px) }

星星镂空实现效果如下

Kapture 2021-11-20 at 13.35.28

完整代码可以查看:backdrop-star (codepen.io)

再比如一个心形❤,实现效果如下

Kapture 2021-11-20 at 13.44.26

完整代码可以查看:backdrop-heart (codepen.io)

只有想不到,没有做不到

七、总结和说明

以上实现了一个鼠标跟随镂空的效果,从简单到复杂,从单一到通用,虽然借助了一点点 JS ,但是仅仅是"工具人"的角色,交互逻辑全部都由 CSS 完成,下面总结一下:

  1. 足够大的阴影是一个实现圆形镂空效果的小技巧
  2. CSS 渐变也能轻易的绘制出圆形镂空背景
  3. 借助 CSS 变量可以很方便的利用鼠标位置实现想要的效果
  4. backdrop-filter 可以想象成磨砂玻璃的功能
  5. CSS Mask 可以给磨砂玻璃打孔,实现镂空的效果
  6. 借助遮罩合成特性和SVG,可以实现任意形状的镂空效果

CSS MASK 还是非常强大的,有必要还是多掌握一下。最后,如果觉得还不错,对你有帮助的话,欢迎点赞、收藏、转发❤❤❤

前端网关踩坑实践

Posted: 21 Nov 2021 08:19 AM PST

项目背景

在后端微服务中,常见的通常会通过暴露一个统一的网关入口给外界,从而使得整个系统服务有一个统一的入口和出口,收敛服务;然而,在前端这种统一提供网关出入口的服务比较少见,常常是各个应用独立提供出去服务,目前业界也有采用微前端应用来进行应用的调度和通信,其中nginx做转发便是其中的一种方案,这里为了收敛前端应用的出入口,项目需要在内网去做相关的部署,公网端口有限,因而为了更好接入更多的应用,这里借鉴了后端的网关的思路,实现了一个前端网关代理转发方案,本文旨在对本次前端网关实践过程中的一些思考和踩坑进行归纳和总结,也希望能给有相关场景应用的同学提供一些解决方面的思路

架构设计

名称作用备注
网关层用来承载前端流量,作为统一入口可以使用前端路由或后端路由来承载,主要作用是流量切分,也可以将单一应用布置于此处,作为路由与调度的混合
应用层用来部署各个前端应用,不限于框架,各个应用之间通信可以通过http或者向网关派发,前提是网关层有接收调度的功能存在不限于前端框架及版本,每个应用已经单独部署完成,相互之间通信需要通过http之间的通信,也可以借助k8s等容器化部署之间的通信
接口层用来从后端获取数据,由于后端部署的不同形式,可能有不同的微服务网关,也有可能有单独的第三方接口,也可能是node.js等BFF接口形式对于统一共用的接口形式可将其上承至网关层进行代理转发

方案选择

目前项目应用系统业务逻辑较为复杂,不太便于统一落载在以类SingleSPA形式的微前端形式,因而选择了以nginx为主要技术形态的微前端网关切分的形式进行构建,另外后续需要接入多个第三方的应用,做成iframe形式又会涉及网络打通之间的问题。由于业务形态,公网端口有限,需要设计出一套能够1:n的虚拟端口的形态出来,因而这里最终选择了以nginx作为主网关转发来做流量及应用切分的方案。

层级方案备注
网关层使用一个nginx作为公网流量入口,利用路径对不同子应用进行切分父nginx应用作为前端应用入口,需要作一个负载均衡处理,这里利用k8s的负载均衡来做,配置3个副本,如果某一个pod挂掉,可以利用k8的机制进行拉起
应用层多个不同的nginx应用,这里由于做了路径的切分,因而需要对资源定向做一个处理,具体详见下一部分踩坑案例这里利用docker挂载目录进行处理
接口层多个不同的nginx应用对接口做了反向代理后,接口由于是浏览器正向发送,因而这里无法进行转发,这里需要对前端代码做一个处理,具体详见踩坑案例后续会配置ci、cd构建脚手架以及一些配置一些常见前端脚手架如:vue-cli、cra、umi的接入插件包

踩坑案例

静态资源404错误

[案例描述] 我们发现在代理完路径后正常的html资源是可以定位到的,但是对于js、css资源等会出现找不到的404错误

[案例分析] 由于目前应用多为单页应用,而单页应用的主要都是由js去操作dom的,对于mv*框架而言通常又会在前端路由及对一些数据进行拦截操作,因而在对应模板引擎处理过程中需要对资源查找进行相对路径查找

[解决方案] 我们项目构建主要是通过docker+k8s进行部署的,因而这里我们想到将资源路径统一放在一个路径目录下,而这个目录路径需要和父nginx应用转发路径的名称相一致,也就是说子应用需要在父应用中需要注册一个路由信息,后续就可以通过服务注册方式进行定位变更等

父应用nginx配置

{     "rj": {         "name": "xxx应用",         "path: "/rj/"     } }
server {     location /rj/ {         proxy_pass http://ip:port/rj/;     } }

子应用

FROM xxx/nginx:1.20.1 COPY ./dist /usr/share/nginx/html/rj/

接口代理404错误

[案例描述] 在处理完静态资源之后,我们父应用中请求接口,发现接口居然也出现了404的查询错误

[案例分析] 由于目前都是前后端分离的项目,因而后端接口通常也是通过子应用的nginx进行方向代理实现的,这样通过父应用的nginx转发过来后由于父应用的nginx中没有代理接口地址,因而会出现没有资源的情况

[解决方案] 有两种解决方案,一种是通过父应用去代理后端的接口地址来进行,这样的话会出现一个问题就是子应用代理的名称如果相同,并且接口并不只是来自一个微服务,或者会有不同的静态代理以及BFF形式,那样对父应用的构建就会出现复杂度不可控的情形;另一种则是通过改变子应用中的前端请求路径为约定好的一种路径,比如加上约定好的服务注册中的路径进行隔离。这里我们兼而有之,对于我们自研项目的接入,会在复应用中进行统一的网关及静态资源转发代理等配置,与子应用约定好路径名,比如后端网关统一以/api/进行转发,对于非自研项目的接入,我们目前需要接入应用进行接口的魔改,后续我们会提供一个插件库进行常见脚手架的api魔改方案,比如vue-cli/cra/umi等,对于第三方团队自研的脚手架构建应用需要自行手动更改,但一般来说自定义脚手架团队通常会有一个统一配置前端请求的路径,对于老应用如以jq等构建的项目,则需要各自手动更改

这里我以vue-cli3构建的方案进行一个示范:

// config export const config = {     data_url: '/rj/api' };
// 具体接口 // 通常这里会做一些axios的路由拦截处理等 import request from '@/xxx'; // 这里对baseUrl做了统一入口,只需更改这里的baseurl入口即可 import { config } from '@/config';  // 具体接口 export const xxx = (params) =>      request({         url: config.data_url + '/xxx'     })

源码浅析

nginx作为一个轻量的高性能web服务器,其架构及设计是极具借鉴意义的,对node.js或其他web框架的设计具有一定的指导思路

nginx是用C语言书写的,因而其将整个架构通过模块进行组合,其中包含了常见的诸如:HTTP模块、事件模块、配置模块以及核心模块等,通过核心模块来调度和加载其它模块,从而实现了模块之间的相互作用

这里我们主要是需要通过location中的proxy_pass对应用进行转发,因而,我们来看一下proxy模块中对proxy_pass的处理

ngx_http_proxy_module

static char *ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);  static ngx_command_t ngx_http_proxy_commands[] = {     {         ngx_string("proxy_pass"),         NGX_HTTP_LOC_CONF | NGX_HTTP_LIF_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_TAKE1,         ngx_http_proxy_pass,         NGX_HTTP_LOC_CONF_OFFSET,         0,         NULL     } };   static char * ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {     ngx_http_proxy_loc_conf_t *plcf = conf;      size_t                      add;     u_short                     port;     ngx_str_t                  *value, *url;     ngx_url_t                   u;     ngx_uint_t                  n;     ngx_http_core_loc_conf_t   *clcf;     ngx_http_script_compile_t   sc;      if (plcf->upstream.upstream || plcf->proxy_lengths) {         return "is duplicate";     }      clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);      clcf->handler = ngx_http_proxy_handler;      if (clcf->name.len && clcf->name.data[clcf->name.len - 1] == '/') {         clcf->auto_redirect = 1;     }      value = cf->args->elts;      url = &value[1];      n = ngx_http_script_variables_count(url);      if (n) {          ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));          sc.cf = cf;         sc.source = url;         sc.lengths = &plcf->proxy_lengths;         sc.values = &plcf->proxy_values;         sc.variables = n;         sc.complete_lengths = 1;         sc.complete_values = 1;          if (ngx_http_script_compile(&sc) != NGX_OK) {             return NGX_CONF_ERROR;         }  #if (NGX_HTTP_SSL)         plcf->ssl = 1; 

No comments:

Post a Comment