关于vue.js:尤大不会说-Rap-的前端不是好前端写一个-vrap-指令

49次阅读

共计 4705 个字符,预计需要花费 12 分钟才能阅读完成。

最近上网多了,倒是乏味的事件也变得多了起来,Vue 的作者尤雨溪也整了段花活,给咱们 Rap 了一段,其实唱歌自身不是很惹眼的事件,然而本是一位十分厉害的开发人员,同时又有比拟 ” 时尚 ” 的说唱喜好,的确是反差感很大,哈哈!

既然尤大都整了,那咱们也来整一小段。让你的输入框也 Rap 起来,如何?

看看成果先:

计划

计划是这样子,咱们实现跳动的 CSS 非常简单,无非是利用 keyframes 给输入框外部的元素套上动画,但如何管制每一个输入框外部的字符,才是比拟辣手的,不过也不算简单,尝试下以下两种计划:

  • 生成一个新的 div,替换本来属于 input 的地位,拷贝 input 的字符组成 div 的外部元素
  • 间接应用可编辑的 div,管制其外部元素的输出款式即可

先筹备一个看起来不唠的网页,根本构造明确了,咱们就进行接下来的的试验:

<input class="input" placeholder="请输出你的 freestyle">

<style>
  body {
    display: flex;
    justify-content: center;
    align-items: center;
    background: #7ec699;
    flex-direction: column;
    height: 100vh;
  }

  .input {
    background: #fff;
    width: 500px;
    height: 50px;
    outline: none;
    border: 5px solid #81d59f;
    text-align: center;
    line-height: 50px;
    border-radius: 25px;
    font-size: 16px;
  }
</style>

鸠占鹊巢

咱们在 input 输出实现之后,监听其失焦事件,创立的 div 完全符合 input 的所有款式,再次点击以后 div 的时候,从新替换回来并聚焦,咱们把这一招能够称为 “ 鸠占鹊巢 ” 式!

如何复制以后元素的所有的款式十分重要,这是咱们须要冲破的点!

咱们能够尝试应用 getComputedStyle 函数来获取 input 的所有款式,并将其提取到 div 之上

尝试写出这个函数:

/**
 * @param {HTMLInputElement} inputEl
 */
function replaceInput(inputEl) {const newDivEl = document.createElement('div')
  const inputStyles = window.getComputedStyle(inputEl)
  for (let styleProp of inputStyles) {newDivEl.style[styleProp] = inputStyles.getPropertyValue(styleProp);
  }
  // 不要遗记 div 和盒模型设置
  newDivEl.style.boxSizing = 'border-box'
  
  // 将其插入到 input 之后
  inputEl.insertAdjacentElement('afterend', newDivEl)
  // 暗藏原有的 input
  inputEl.style.display = 'none'
  
  return newDivEl
}

有了这个函数就很简略啦,咱们绑定 blur 事件,并且 鸠占鹊巢

const inputEl = document.querySelector('.input')
inputEl.addEventListener('blur', (e) => {if (e.target.value) {const newDivEl = replaceInput(inputEl)
    newDivEl.innerHTML = e.target.value
    
    // 在这里咱们须要实现 div 点击后,input 主动聚焦的问题
  }
})

到这里你就会发现一个问题,如果咱们间接替换了 input,那么其设置的 line-height 属性会导致 div 的字体靠下,因为 input 同时设置了 border 属性,要解决这个问题,咱们必须强制 div 垂直居中,并且不能影响 input 上设置好的 text-align 属性!

咱们须要批改刚刚申明的替换办法:

function replaceInput(inputEl) {
  ...
  // 不要遗记 div 和盒模型设置
  newDivEl.style.boxSizing = 'border-box'

  // 因为 input 设置了 `line-height` 和 `broder`,所以咱们须要垂直居中,采纳 FlexBox 的形式来解决
  // 同时 input 也设置了 `text-align`,所以咱们须要将其转换为 `justify-content`
  newDivEl.style.display = 'inline-flex'
  newDivEl.style.alignItems = 'center'
  if (newDivEl.style.textAlign === 'center') {newDivEl.style.justifyContent = 'center'} else if (newDivEl.style.textAlign === 'right') {newDivEl.style.justifyContent = 'flex-end'}

  // 将其插入到 input 之后
  ...
}

OK,这样咱们就绝对完满的解决了上述问题,当初咱们须要在 div 点击之后,主动切换回 input,并聚焦:

newDivEl.addEventListener('click', () => {
  inputEl.style.display = 'inline-block'
  inputEl.focus()
  newDivEl.parentNode.removeChild(newDivEl)
})

当初就让咱们的文字 Rap 起来吧,增加一个动画子元素:

@keyframes jumping {
  25% {transform: translateY(-10px);
  }
  75% {transform: translateY(10px);
  }
}
function appendJumpingLetters(value, parentNode) {for (let i = 0; i < value.length; i++) {const spanEl = document.createElement('span')
    spanEl.innerText = value[i]
    // 动画,给每一个动画加上提早
    const delay = -i * 0.3
    spanEl.style.animation = 'jumping 0.8s linear infinite' + delay + 's'
    parentNode.appendChild(spanEl)
  }
}

// 替换掉刚刚的 newDivEl.innerHTML = e.target.value
appendJumpingLetters(e.target.value, newDivEl)

可编辑的 div

如果要更不便的实现这个性能,咱们须要应用可编辑的 div,这样子的话,不须要创立新的元素就能够间接管制输入框外部的子元素。

咱们来看看怎么实现吧!先筹备一个可编辑的 div !

<div contenteditable class="input"></div>

因为咱们能够很不便的管制 div 的款式,所以批改一下下面的款式,让 line-heightheight 实现的居中成果不要被 border 影响!

.input {
  --border-width: 5px;

  background: #fff;
  width: 500px;
  height: 50px;
  outline: none;
  border: var(--border-width) solid #81d59f;
  border-radius: 25px;
  font-size: 16px;
  box-sizing: border-box;
  text-align: center;
  line-height: calc(50px - var(--border-width) * 2);
}

接下来就很简略了,只须要监听失焦事件后,替换其子元素就妥啦,一步到位如何?

const inputEl = document.querySelector('.input')
inputEl.addEventListener('blur', (e) => {const text = inputEl.innerText.trim()
  inputEl.innerHTML = ''
  if (text) {
    // 不加 setTimeout 的时候,div 无奈顺利失焦
    setTimeout(() => appendJumpingLetters(text, inputEl))
  }
})

inputEl.addEventListener('focus', (e) => {inputEl.innerHTML = inputEl.innerText.trim()
})

看看成果吧:

v-rap

既然是尤大提供的灵感,那么咱们天然不能遗记 Vue,同时咱们还须要反对两种不同的模式:

  • 如果指令用在了 input,就应用第一中形式去执行
  • 如果指令用在了 div, 就应用第二种形式
<div id="app">
  <header>v-rap</header>

  <input class="input" v-rap>
  <div contenteditable class="input" v-rap></div>
</div>

OK,定义咱们的指令:

<script type="module">
  import {createApp} from 'vue'
  const app = createApp({})
  
  const vRap = {mounted(el) {console.log(el)
    }
  }
  app.directive('rap', vRap)
  app.mount('#app')
</script>

接下来,咱们须要判断以后的指令的作用域是在 input 或者 div 上,而后应用相应的函数进行管制就 OK 啦!

function withElType(el) {
  const type = el.tagName
  switch (type) {
    case 'DIV':
      const text = el.innerText.trim()
      el.innerHTML = ''
      if (text) {setTimeout(() => appendJumpingLetters(text, el))
      }
      break;
    case 'INPUT':
      if (el.value) {const newDivEl = replaceInput(el)
        appendJumpingLetters(el.value, newDivEl)

        newDivEl.addEventListener('click', () => {
          el.style.display = 'inline-block'
          el.focus()
          newDivEl.parentNode.removeChild(newDivEl)
        })
      }
      break;
  }
}

const vRap = {mounted(el) {el.addEventListener('blur', () => {withElType(el)
    })

    if (el.tagName === 'DIV') {el.addEventListener('focus', () => {el.innerHTML = el.innerText.trim()
      })
    }
  }
}

本人入手点击看看?

jcode

结语

一些乏味的小试验,能够激发咱们的学习乐趣与欲望,我始终奉行的一句话是:” 趣味是学习的惟一能源!”,心愿诸位在后退的道路上一帆风顺!

一道思考题:如果我想在 element-ui 的 el-input 上应用该指令,要怎么办呢?

我是泰罗凹凸曼,M78 星云最爱写代码的,咱们下一篇再会!

去摸索,不晓得的货色还多着呢!

正文完
 0