最近上网多了,倒是乏味的事件也变得多了起来,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.valueappendJumpingLetters(e.target.value, newDivEl)
可编辑的div
如果要更不便的实现这个性能,咱们须要应用可编辑的div,这样子的话,不须要创立新的元素就能够间接管制输入框外部的子元素。
咱们来看看怎么实现吧!先筹备一个可编辑的 div !
<div contenteditable class="input"></div>
因为咱们能够很不便的管制 div
的款式,所以批改一下下面的款式,让 line-height
和 height
实现的居中成果不要被 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星云最爱写代码的,咱们下一篇再会!
去摸索,不晓得的货色还多着呢!