• 「原生练手」搜索框点击展开特效让你来实现,你会如何实现?


    点击搜索按钮展开搜索输入框这样一个简单的功能,如果让你实现你会怎么实现呢?欢迎在评论区留言讨论

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gpaadC1O-1658297401556)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4e6e1a4c20f443719481caac3b5eadea~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image)]

    1. 思路分析

    首先分析整个搜索输入框可以发现,我们至少需要一个输入框和一个搜索按钮,所以可以考虑把它们放进一个容器里面,然后给容器添加一个特殊的类名.active,用来区分搜索框点击和未被点击时的样式,这个容器就叫.search-container

    为什么是给容器添加.active而不是单独给输入框和搜索按钮添加呢?因为点击搜索按钮后,不仅仅涉及到将输入框展开,还涉及到搜索按钮的位置变动,都是属于容器激活时才要处理的样式,所以将类名添加在.active上更为合适

    然后我们还需要理清一下整个功能涉及到哪些事件:

    1. 点击搜索按钮
      1. 给整个容器添加一个.active类名,并且在css中为这个类名添加样式,将输入框的宽度变大,同时使用transition添加过渡效果
      2. 将焦点移至input输入框中
    2. 输入框失去焦点
      1. 失去焦点后应当将容器的.active类名移除,让输入框和搜索按钮恢复原来的状态

    2. HTML 结构

    根据以上的思路分析,我们可以先写出整体的HTML结构

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    结构很简单,没什么好说的


    3. CSS 样式

    3.1 搜索按钮的移动

    由于我们希望搜索按钮会动起来,那么就涉及到一个问题,它应该相对于谁动?肯定是容器!所以我们首先要将容器的position设置为relative,这样就可以让按钮相对于容器动起来

    .search-container {
      position: relative;
      height: 50px;
    } 
    
    • 1
    • 2
    • 3
    • 4

    由于按钮是容器的直接子元素,所以position设置成absolute后会相对于容器进行定位

    .btn {
      position: absolute;
      inset: 0 auto auto 0;
      width: 50px;
      height: 100%;
      transition: transform 0.5s ease;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    inset: 0 auto auto 0相当于top: 0; left: 0auto相当于不设置,整个inset的四项指代的是上、右、下、左属性

    由于按钮位置会发生变化,所以添加transition属性让过渡更加自然,由于我们是通过transform: translateX进行位置变换的,所以这里transition的作用目标是transofrm,如果采用lef进行位置变更,就将它改成left即可

    既然要让按钮动起来,那肯定要有一个类名来控制,当这个类名出现的时候就移动到一个位置,消失时又移动回来,所以写出如下样式:

    .search-container.active .btn {
      transform: translateX(160px);
    } 
    
    • 1
    • 2
    • 3

    意思是当父容器有active类名的时候,将按钮的位置向右偏移160px


    3.2 搜索输入框的宽度变化

    当容器有active类名时,改变输入框的宽度

    .search-container.active .search-input {
      width: 200px;
    } 
    
    • 1
    • 2
    • 3

    再写好输入框的完整样式:

    .search-input {
      height: 100%;
      width: 50px;
      border: 0;
      padding: 15px;
      font-size: 24px;
      background-color: #fff;
      transition: width 0.5s ease;
      border-radius: 20px;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这里由于涉及到变化的属性只是width,所以transition作用的目标属性是width


    4. JS 处理事件监听

    4.1 获取元素

    首先我们要获取到整个功能涉及的元素,搜索按钮肯定要的了,因为我们要监听它的点击事件,输入框也是肯定要的,因为我们要监听它的失去焦点blur事件,容器更是少不了,因为我们的特殊类名.active就是添加在它身上

    明确了要获取的元素之后,就可以写出如下代码:

    /** @type HTMLElement */
    const oBtn = document.querySelector('.btn')
    /** @type HTMLElement */
    const oSearchInput = document.querySelector('.search-input')
    const oSearchContainer = document.querySelector('.search-container') 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里使用了jsdoc的方式显示指明了元素的类型,这样能够获得明确的类型提示,比如添加事件监听器的时候,因为默认的querySelector获取到的是Element类型的对象,那么事件只有fullscreenchangefullscreenerror两种 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l7zoKcgF-1658297401556)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3ce06a66e53d442b870d1ab81d2e60e2~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image)] 如果显式声明元素类型后,就可以获得更加友好的类型提示 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vzGXCKzp-1658297401557)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/de3fd0da2a1749dea235ed2dcb4ef1f2~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image)]


    4.2 搜索按钮点击事件监听器

    通过前面的分析我们已经知道,首先需要给容器添加.active类名

    其次,还要将元素焦点聚焦至输入框中,这个可以通过DOM元素对象的focus方法实现

    /**
     * @description 点击搜索按钮给容器添加 active 类名
     */
    const handleBtnClick = () => {
      oSearchContainer.classList.add('active')
      oSearchInput.focus()
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.3 输入框失去焦点事件监听器

    当输入框失去焦点的时候,我们就应当移除容器的active类名,这样就会由相应的CSS将元素归为

    /**
     * @description 搜索输入框失去焦点时移除容器的 active 类名
     */
    const handleBlur = () => {
      oSearchContainer.classList.remove('active')
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    至此,一个简单的点击展开搜索框特效就实现完成了,完整代码如下

    
    
      
        
        
        
        
        
        
        hidden-search
      
      
        
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    * {
      box-sizing: border-box;
    }
    
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      background-color: #292d3e;
      margin: 0;
      padding: 0;
    }
    
    .search-container {
      position: relative;
      height: 50px;
    }
    
    .search-input {
      height: 100%;
      width: 50px;
      border: 0;
      padding: 15px;
      font-size: 24px;
      background-color: #fff;
      transition: width 0.5s ease;
      border-radius: 20px;
    }
    
    .btn {
      position: absolute;
      inset: 0 auto auto 0;
      width: 50px;
      height: 100%;
      cursor: pointer;
      font-size: 24px;
      border: 0;
      background-color: #fff;
      transition: transform 0.5s ease;
      border-radius: 20px;
    }
    
    .btn:focus,
    .search-input:focus {
      outline: none;
    }
    
    .search-container.active .search-input {
      width: 200px;
    }
    
    .search-container.active .btn {
      transform: translateX(160px);
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    ;(() => {
      /** @type HTMLButtonElement */
      const oBtn = document.querySelector('.btn')
      /** @type HTMLInputElement */
      const oSearchInput = document.querySelector('.search-input')
      const oSearchContainer = document.querySelector('.search-container')
    
      /**
       * @description 点击搜索按钮给容器添加 active 类名
       */
      const handleBtnClick = () => {
        oSearchContainer.classList.add('active')
        oSearchInput.focus()
      }
    
      /**
       * @description 搜索输入框失去焦点时移除容器的 active 类名
       */
      const handleBlur = () => {
        oSearchContainer.classList.remove('active')
      }
    
      const bindEvent = () => {
        oBtn.addEventListener('click', handleBtnClick)
        oSearchInput.addEventListener('blur', handleBlur)
      }
    
      const init = () => {
        bindEvent()
      }
    
      init()
    })() 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
  • 相关阅读:
    Python Flask
    redis缓存穿透、击穿、雪崩介绍
    机器人课程教师面对的困境有哪些(补充)
    MySQL SQL100道基础练习题
    源代码防泄密是什么?
    C# 连接Linux中的redis
    【图解RabbitMQ-7】图解RabbitMQ五种队列模型(简单模型、工作模型、发布订阅模型、路由模型、主题模型)及代码实现
    @RequestParam注解的正确使用方式
    Destroying Bridges(Round 934)
    千兆以太网协议简介
  • 原文地址:https://blog.csdn.net/qq_53225741/article/details/125891167