【Web Component】图片滑动验证组件

467 阅读4分钟

突然对图片拖动这样验证方式感兴趣,简单写了一个 demo 出来。但是光写这个感觉太单调,然后就加上了Web Component,简单了解了一下这个原生组件的方案。

效果

2021-12-29 16.44.18.gif 稍微有点简陋😂

Web Component

原生的组件化方案,具体就不多说了,详细可以查看 MDN,本次也只是简单了解了一下,上图整个效果都是封装在 img-validator 中(只是效果,相关属性事件都没写)。

<!-- 引入后直接使用 -->
<img-validator></img-validator>

渲染出来是这样的: image.png

想要自定义组件,需要使用 customElements.define(ElementName, CustomElementClass, options); 来进行定义,其中 ElementName 必须使用短横线,CustomElementClass 是用来实例化 dom 对象的,理论上使用构造函数的方式也行,options 是一些其他选项。

自定义元素的类里面直接像写普通类就行,没啥特别的限制,开发时可以直接继承现有的节点类,可以减少很多工作量,如果从 0 开始写的话就需要考虑 slots、属性以及事件等细节。

// 必须继承 HTMLElement 或者其他更明确的节点
class CustomEl extends HTMLElement {
  /// ....
  
  constructor() {
    super();
    
    // 对于属性的处理,可以利用 hasAttribute 来判断,然后直接 setAttribute 到任何想要设置的节点上
    // 这样在自定义组件上的属性才会生效
    if (this.hasAttribute('style')) {
      wrapper.setAttribute('style', this.getAttribute('style'));
    }
  }
}

// 然后就可以直接 html 中使用了
customElements.define('custom-el', CustomEl);

事件这次没涉及到,可以利用自定义事件和 this.dispatchEvent() 来触发

继承类似 HTMLUListElement 的节点时,在使用时有些许不同

<!-- 继承 HTMLElement 时 -->
<custom-el></custom-el>

<!-- 继承 HTMLUListElement 时 -->
<ul is="custom-el"></ul>

需要注意的是,这种处于 shadow-root 中的元素在外部不能直接获取到,必须先获取到宿主节点,比如 img-validator,然后利用宿主上的 shadowRoot 获取到组件的根节点(必须是处于 open mode 才能拿到),然后就可以在这个节点上使用选择器查找 dom 节点。

组件内部的 dom 结构可以自己一个一个节点创建,也可以简单粗暴直接 root.innerHTML = '<div>23333</div>',还可以利用 templatetemplate 不会在页面上渲染出来,这样就可以在实例化自定义组件时通过选择器获取到模板节点,然后直接插入。

周期钩子

  • connectedCallback() 首次插入文档中后被调用,本次用来初始化事件监听

  • disconnectedCallback() 被删除后调用,本次用来移出事件监听器

还有两个请参考 MDN 自定义元素生命周期

滑动校验

首先我们需要先把图片展示出来,其次需要一个不会移动的目标区域,还要有一个可以移动的区域,所以整体的 dom 结构就确定下来了

<div class="img-wrapper">
    <img src id="img" />
    <div id="moveTicket"></div>
    <div id="targetTicket"></div>
</div>
<!-- 滑块 -->
<div class="slider">
    <div class="slider-line"></div>
    <div class="slider-handle" id="slider-handle"></div>
</div>

拖动就不说了,就监听鼠标事件就行,但是这样会存在一些体验问题,暂时还没想好怎么解决,就是在快速滑动时,容易卡在一个地方动不了,不过这不是重点,我们慢慢拖动就行

在图片加载完成后,会随机初始化一下目标区域(即 #targetTicket)的位置(这个应该需要后端生成比较合理),然后给移动区域(#moveTicket)添加背景图,这主要是为了营造出碎片的错觉,利用背景图片的定位(只需要将目标区域的坐标取相反数然后提供给背景图就行,即目标区域坐标 (x, y),背景图定位 el.style.backgroundPosition = -${x}px -${y}px 就可以找到对应区域了)

clip-path

闲着没事为了更加贴近实际的验证,特意添加了这种拼图效果 image.png

这是使用 css 中的 clip-path 实现的

clip-path: path('M 0 0 v 30 A 7.5 7.5, 0, 1, 1, 0 45 v 15 h 15 A 7.5 7.5, 0, 1, 1, 30 60 h 30 v -60 z');

手写这个路径真痛苦又学到了新知识,clip-path 可以使用和 svg 中的 path 一样的语法来创建复杂的图形,这里就不过多介绍了。

验证

这里使用了偷懒的假验证,直接判断移动区域的 offsetLeft 是不是在预期的区间中,是的话就通过验证,实际上应该将位置交给后端来验证更为合理,当然这里就不需要了。

结尾

总体实现都比较简陋,只是满足了一下自己的好奇心。也体会到现代前端框架的几个优点,组件的 html 模板代码都可以非常简单快捷的编写,事件也不需要自己操心,针不戳 4bb74fad-8c54-4482-a1ce-d05b11a5bf2d.gif