H5: div与textarea输入框的交互(聚焦、失去焦点、键盘收起)

这篇具有很好参考价值的文章主要介绍了H5: div与textarea输入框的交互(聚焦、失去焦点、键盘收起)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

简介

本文是基于 VUE3+TS 的代码说明。

记录自己遇到的 div 与 textarea 输入框交互的聚焦、失去焦点、键盘收起、表情插入不失去焦点的需求实现。

需求分析

textarea聚焦,web前端之H5,vue.js,前端,html5textarea聚焦,web前端之H5,vue.js,前端,html5

1.固定在页面底部;
2.默认显示纯文字与发送图标按钮,文字超出的省略显示;
3.点击文字后,显示文本输入框、表情、半透明遮罩层,自动聚焦;
4.有输入内容时,文本输入框右侧显示发送按钮;
5.点击表情,将表情加入到输入框最后,并且输入法键盘不收起;
6.输入框失去焦点、点击键盘上的收起或完成时,隐藏文本输入框和表情,显示默认的纯文字样式。

注意

------以下代码是伪代码------

1.输入框聚焦后,可能存在输入框位置不正确的问题

如输入框被遮挡、输入框没有挨着键盘等类似的问题。
这些问题在网上的解决方案较多,可自行查阅。

我的处理思路如下:

// html
<Teleport to="#inputPosition">
 <div v-show="isTextareaFocus" class="textarea-box">
    <!-- 输入框与发送按钮 -->
    <div>
      <textarea ref="textareaRef" />
      <button>发送</button>
    </div>
    <!-- 表情 -->
    <div>
      <div v-for="(emoji, index) in emojiList" :key="index">{{ emoji }}</div>
    </div>
  </div>
</Teleport>

点击文本div时,显示文本输入框,并且自动聚焦

<script setup lang="ts">
  import { ref, nextTick } from 'vue'

  const isTextareaFocus = ref(false) // 文本输入框是否聚焦(即显示)
  const textareaRef = ref() // 输入框对应的DOM
  const emojiList = ['👍', '😀', '😮', '🥰', '😡', '🤣', '😤', '🙏'] // '🫡', '🫰🏻'

  /** 方法:输入框文本-是否聚焦、显示 */
  const displayTextarea = (display = false) => {
    isTextareaFocus.value = display
  }

  /** 操作:点击文本div */
  const handleToFocus = () => {
    displayTextarea(true)
    nextTick(() => {
      textareaRef.value?.focus() // 聚焦
      // 部分ios上添加表情后,光标不在最后位置的问题处理
      const length = message.value.length
      textareaRef.value?.setSelectionRange(length, length)
    })
  }
</script>

2.键盘按钮的收起,判断输入框是否失去焦点:

1)Android上,键盘按钮的收起,大部分不会触发输入框的blur事件,会触发webview的改变;
2)IOS上,键盘按钮的收起,会触发输入框的blur事件,大部分不会触发webview的改变;
3)点击表情时,也会导致输入框失去焦点。

我的处理思路如下:
1)默认都有的处理逻辑

/** 进行手势操作时的过滤处理:如点击、滑动等 */
const touchStartEvent = (e: Event) => {
  const target = e.target as HTMLElement
  // 这里包含textareaBtn,是为了发送按钮的点击事件能正常触发
  if (target.id === 'emoji' || target.id === 'textareaBtn') {
    isNeedFocus.value = true
  } else {
    isNeedFocus.value = false
  }
}

2)ios的特殊处理逻辑

if (val) {
  // 键盘弹起
  const focusEl = textareaRef.value
  if (focusEl) {
    focusEl.scrollIntoView({ block: 'center' })
  }
 } else {
  // 键盘收起
  clickBlur()
}

3.表情的插入

整个列表、文本输入框盒子添加touchstart事件,最先执行的是touchstart,根据当前touch事件的触发dom的id,判断是否需要保留文本输入框的聚焦;然后执行的表情的点击事件以及文本输入框的失去焦点事件,其中:
1)touchStartEvent
判断触发的dom的id是否是需要保留聚焦的dom,做一个标记;
2)handleInsertEmoji
做表情的插入,以及对文本输入框的聚焦;
3)handleToBlur
做输入框失去焦点的逻辑处理,根据1)中的标记,进行逻辑处理(之所以要重置标记,是为了下次输入框能正常失去焦点)。

// html
<div class="page" @touchstart="touchStartEvent">
  ...
  <!-- 文本输入框、表情栏 -->
  <Teleport to="#inputPosition">
    <div v-show="isTextareaFocus" class="textarea-box" @touchstart="touchStartEvent">
      ...
        <textarea @blur="handleToBlur" />
      ...
      <!-- 表情 -->
      <div class="emoji-list">
        <div
          id="emoji"
          v-for="(emoji, index) in emojiList"
          :key="index"
          @click.stop="handleInsertEmoji(emoji)"
          >{{ emoji }}</div
        >
      </div>
    </div>
  </Teleport>
</div>

// ts
  /** 进行手势操作时的过滤处理:如点击、滑动等 */
  const touchStartEvent = (e: any) => {
    // 这里包含textareaBtn,是为了发送按钮的点击事件能正常触发
    if (e.target.id === 'emoji' || e.target.id === 'textareaBtn') {
      isNeedFocus.value = true
    } else {
      isNeedFocus.value = false
    }
  }
 
  /** 操作:表情 */
  const handleInsertEmoji = (emoji: string) => {
    if (message.value.length >= messageLength) {
      return
    }
    message.value += emoji
    nextTick(() => {
      handleToFocus()
    })
  }

  /** 文本输入框失去焦点时的逻辑处理 */
  const handleToBlur = () => {
    if (isNeedFocus.value) {
      isNeedFocus.value = false
      return
    }
    displayTextarea(false)
  }

具体实现

目录结构
/test
/test/utils.ts
/test/index.vue

1.utils.ts

import { ref } from 'vue'

enum UserTerminalEnum {
  ANDROID,
  IOS,
  WEB
}

/** 获取当前所在客户端的类型 */
const getUserTerminalType = (): UserTerminalEnum => {
  const u = navigator.userAgent
  const isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1 // 判断是否是 android终端
  const isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/) // 判断是否是 iOS终端
  if (isAndroid) {
    return UserTerminalEnum.ANDROID
  }
  if (isIOS) {
    return UserTerminalEnum.IOS
  }
  return UserTerminalEnum.WEB
}

const isNotIOS = getUserTerminalType() !== UserTerminalEnum.IOS

/**
 * 防抖函数
 * @param fn 回调函数
 * @param wait 等待时间
 * @param immediate 是否立即出发
 */
function debounce<T extends any[], U>(
  fn: (...args: T) => U,
  wait: number = 300,
  immediate: boolean = false
) {
  if (typeof fn !== 'function') throw new Error('must have a callback fn')
  if (typeof wait === 'boolean') immediate = wait
  let timer: NodeJS.Timer | null = null // 注意需要在.eslintrc.cjs中开启 globals: { NodeJS: true }
  return function proxy(_this: any, ...args: T) {
    const _self = _this
    const _immediate = immediate && !timer
    timer && clearTimeout(timer)
    timer = setTimeout(() => {
      !immediate && fn.apply(_self, args)
      timer = null
    }, wait)
    _immediate && fn.apply(_self, args)
  }
}

const width = ref(0) // 页面可视宽度
const height = ref(0) // 页面可视高度
// const outerWidth = ref(0)
// const outerHeight = ref(0)
// const screenWidth = ref(0)
// const screenHeight = ref(0)
// const screenRatio = ref(0)
// const isLandScape = ref(false)
const INPUT_EL_TAG_NAMES = ['INPUT', 'TEXTAREA']
const isKeyboardVisible = ref(false) // 键盘是否弹起
let isBind = false

const getKeyboardVisible = (newHeight: number): boolean => {
  const { activeElement } = document
  if (!activeElement) return false
  const activeElTagName = activeElement.tagName
  return newHeight <= height.value && INPUT_EL_TAG_NAMES.includes(activeElTagName)
}

// const getIsLandScape = () => {
//   const match = window.matchMedia('(orientation: landscape)')
//   return !!match.matches
// }

const getSize = () => {
  const { innerHeight, innerWidth, outerWidth: ow, outerHeight: oh, screen } = window
  const _isKeyboardVisible = getKeyboardVisible(innerHeight)
  isKeyboardVisible.value = _isKeyboardVisible
  height.value = innerHeight
  width.value = innerWidth
  // outerWidth.value = ow
  // outerHeight.value = oh
  // screenWidth.value = screen.availWidth
  // screenHeight.value = screen.availHeight
  // isLandScape.value = _isKeyboardVisible ? false : getIsLandScape()
  // if (!_isKeyboardVisible) {
  //   screenRatio.value = height.value / width.value
  // }
}

const useClientWindowInfo = () => {
  if (!isBind) {
    getSize()
    window.addEventListener('resize', debounce(getSize, 200))
    window.addEventListener('fullscreenchange', () => {
      setTimeout(getSize, 500)
    })
    isBind = true
  }
  return {
    width,
    height,
    isKeyboardVisible
  }
}

export { UserTerminalEnum, isNotIOS, debounce, useClientWindowInfo }

3.index.vue

<template>
  <div class="page" @touchstart="touchStartEvent">
    <!-- 遮罩 -->
    <div v-if="isTextareaFocus" class="mask-box" @touchstart="clickBlur" />
    <!-- 文字展示栏 -->
    <div class="input-area">
      <div class="input-text-box">
        <!-- 文字展示 -->
        <div class="input-text" @click="handleToFocus">
          {{ message || placeholderText }}
        </div>
        <!-- 发送图标按钮 -->
        <div
          class="btn-input"
          :class="{ 'btn-input-active': message?.length }"
          @click="handleSend"
        />
      </div>
    </div>
    <!-- 文本输入框、表情栏 -->
    <div v-show="isTextareaFocus" class="textarea-box" @touchstart="touchStartEvent">
      <!-- 输入框与发送按钮 -->
      <div class="textarea-row">
        <textarea
          ref="textareaRef"
          v-model="message"
          :class="message.length ? 'textarea-none' : 'textarea-active'"
          class="textarea-normal"
          :placeholder="placeholderText"
          :contenteditable="true"
          name="textarea"
          rows="5"
          cols="50"
          :maxlength="messageLength"
          @blur="handleToBlur"
        />
        <button
          id="textareaBtn"
          :style="{
            opacity: message.length ? '1' : '0',
            'transition-delay': message.length ? '200ms' : '0ms'
          }"
          @click.stop="handleSend"
          >发送</button
        >
      </div>
      <!-- 表情 -->
      <div class="emoji-list">
        <div
          id="emoji"
          v-for="(emoji, index) in emojiList"
          :key="index"
          @click.stop="handleInsertEmoji(emoji)"
          >{{ emoji }}</div
        >
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
  import { ref, nextTick, watch } from 'vue'
  import { isNotIOS, useClientWindowInfo } from './utils'

  const { isKeyboardVisible } = useClientWindowInfo()

  const placeholderText = ref('尽情反馈您的建议哦~')
  const message = ref('') // 输入框内容
  const isTextareaFocus = ref(false) // 文本输入框是否聚焦(即显示)
  const textareaRef = ref() // 输入框对应的DOM
  const messageLength = 200
  const emojiList = ['👍', '😀', '😮', '🥰', '😡', '🤣', '😤', '🙏'] // '🫡', '🫰🏻'
  const isNeedFocus = ref(true) // 是否需要焦点

  /** 方法:输入框文本-是否聚焦、显示 */
  const displayTextarea = (display = false) => {
    isTextareaFocus.value = display
  }

  /** 方法:输入框聚焦 */
  const handleToFocus = () => {
    displayTextarea(true)
    nextTick(() => {
      textareaRef.value?.focus() // 聚焦
      if (isNotIOS) {
        // 部分ios上添加表情后,光标不在最后位置的问题处理
        const length = message.value.length
        textareaRef.value?.setSelectionRange(length, length)
        // 部分ios上添加表情后,没有自动滑动到底部
        textareaRef.value.scrollTop = textareaRef.value.scrollHeight
      }
    })
  }

  /** 文本输入框失去焦点时的逻辑处理 */
  const handleToBlur = () => {
    if (isNeedFocus.value) {
      isNeedFocus.value = false
      return
    }
    displayTextarea(false)
  }

  /** 进行手势操作时的过滤处理:如点击、滑动等 */
  const touchStartEvent = (e: Event) => {
    const target = e.target as HTMLElement
    // 这里包含textareaBtn,是为了发送按钮的点击事件能正常触发
    if (target.id === 'emoji' || target.id === 'textareaBtn') {
      isNeedFocus.value = true
    } else {
      isNeedFocus.value = false
    }
  }

  /** 操作:键盘弹出时,点击蒙层,关闭输入 */
  const clickBlur = () => {
    if (textareaRef.value) {
      textareaRef.value.blur()
    }
    displayTextarea(false)
  }

  /** 操作:添加表情 */
  const handleInsertEmoji = (emoji: string) => {
    if (message.value.length >= messageLength) {
      return
    }
    message.value += emoji
    nextTick(() => {
      handleToFocus()
    })
  }

  /** 操作:点击发送 */
  const handleSend = () => {
    console.log('发送消息', message.value)
    message.value = ''
  }

  /** 监听:键盘弹起/收起时 */
  watch(
    () => isKeyboardVisible.value,
    (val: boolean) => {
      if (!isNotIOS) {
        return
      }
      if (val) {
        // 键盘弹起
        const focusEl = textareaRef.value
        if (focusEl) {
          focusEl.scrollIntoView({ block: 'center' })
        }
      } else {
        // 键盘收起
        clickBlur()
      }
    }
  )
</script>

<style scoped lang="less">
  .page {
    width: 100vw;
    height: 100vh;
    position: relative;
    background-color: #141624;
    .mask-box {
      position: absolute;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      opacity: 0.5;
      background-color: #000;
    }
    .input-area {
      height: 82px;
      padding: 10px 12px 0px;
      position: absolute;
      right: 0;
      bottom: 0;
      left: 0;
      border-top: 1px solid #272937;
      background-color: #141624;
      .input-text-box {
        height: 40px;
        padding: 0 15px;
        border-radius: 20px;
        background-color: #272937;
        display: flex;
        align-items: center;
        .input-text {
          flex: 1;
          line-height: 40px;
          font-size: 16px;
          color: #939191;
          overflow: hidden;
          white-space: nowrap;
          text-overflow: ellipsis;
        }
        .btn-input {
          margin-left: 10px;
          width: 22px;
          height: 22px;
          border-radius: 5px;
          background-color: #939191;
        }
        .btn-input-active {
          background-color: #3994f9;
        }
      }
    }
  }
  .textarea-box {
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 9999;
    border-top: 1px solid #272937;
    background-color: #141624;
    .textarea-row {
      display: flex;
      align-items: flex-end;
      position: relative;
      padding: 10px;
      .textarea-normal {
        padding: 10px;
        height: 90px;
        background-color: #272937;
        color: #fff;
        border: none;
        outline: none;
        inline-size: none;
        resize: none;
        border-radius: 8px;
        font-size: 15px;
        transition-duration: 0.2s;
        transition-timing-function: ease;
        -webkit-user-select: text !important;
      }
      .textarea-none {
        width: calc(100% - 92px);
        transition-delay: 0ms;
      }
      .textarea-active {
        width: calc(100% - 20px);
        transition-delay: 200ms;
      }
      #textareaBtn {
        width: 62px;
        height: 31px;
        line-height: 31px;
        text-align: center;
        position: absolute;
        right: 10px;
        bottom: 10px;
        border-radius: 15px;
        border: none;
        background-color: #3994f9;
        overflow: hidden;
        white-space: nowrap;
        color: #fff;
        font-size: 15px;
        transition-duration: 0.2s;
        transition-timing-function: ease;
      }
    }
    .emoji-list {
      height: 50px;
      display: flex;
      align-items: center;
      #emoji {
        width: calc(100% / 8);
        height: 100%;
        text-align: center;
        font-size: 30px;
      }
    }
  }
</style>

最后

觉得有用的朋友请用你的金手指点一下赞,或者评论留言一起探讨技术!文章来源地址https://www.toymoban.com/news/detail-661569.html

到了这里,关于H5: div与textarea输入框的交互(聚焦、失去焦点、键盘收起)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • uniapp微信小程序 实现评论键盘弹出的时候 有两个输入框,第一个输入框被禁用并绑定了点击事件,点击后想要触发第二个输入框获取焦点并弹出键盘。但是在 iOS 真机上点击后键盘会短暂失去焦点

    问题 :我现在有一个需求就是 要实现输入评论  有两个输入框,第一个输入框被禁用并绑定了点击事件,点击后想要触发第二个输入框获取焦点并弹出键盘。但是在 iOS 真机上点击后键盘会短暂失去焦点  安卓真机测试没有问题 原因 : 1. iOS 上输入框聚焦有一个显式的动画过

    2024年02月04日
    浏览(39)
  • h5键盘弹起遮挡输入框的处理

    一、前言: 混合开发中遇到一个问题,有些型号的安卓机和ios机型,输入框唤起键盘后,输入框会被键盘遮挡,需要手动滑动才能漏出来,影响用户体验 二、解决办法: 1.ios和android手机唤起的windows事件不一样,要分别处理 2.document.body.scrollTop无效,可以用document.documentElemen

    2024年02月15日
    浏览(33)
  • 移动端H5页面在input输入框获得焦点时禁止唤起键盘

    移动端 实现效果: 当input输入框获得焦点时,在保留光标的情况下,又不让手机虚拟键盘弹起 问题背景: 哈哈哈哈 我又来了,又是java安卓应用嵌入H5页面,给大家看下效果 点击开始时间或者结束时间时会弹出日期选择器,这个时候呢在手机上看的话,会同时触发键盘,导

    2024年02月16日
    浏览(37)
  • 【006JavaScript 弹窗】掌握警告框、确认框和输入框的交互技巧

    本教程将介绍如何在 JavaScript 中创建弹窗。弹窗是一种常见的用户界面元素,用于显示重要的信息、警告、确认对话框等。通过 JavaScript,您可以通过使用 alert() 、 confirm() 和 prompt() 函数来创建不同类型的弹窗。 alert() 函数用于显示一个包含文本消息和一个“确定”按钮的简

    2024年02月10日
    浏览(37)
  • Android EditText 获取/失去焦点

    项目的需求中,又一个4位数的验证码界面,小弟才疏学浅,只想到了用 线性布局里面放四个EditText 。 需求需要输入内容后,自动跳到下一个位置聚焦,删除指定位置后,自动跳到上一个位置聚焦,由于聚焦/非聚焦UI展示得都不同,所以每个editText都会频繁的设置焦点变化。

    2024年02月03日
    浏览(32)
  • WPF中TextBox失去焦点事件

    限制TextBox只能输入整数,而且整数的数值范围为0-100。如果输入101后,弹窗提示输入超限 MainWindow.xaml MainWindow.xaml.cs

    2024年04月23日
    浏览(25)
  • Button按钮:得到鼠标焦点后自动放大,失去鼠标焦点后自动缩小

    程序设计过程中,我们经常需要增加一些动态效果,以此改善用户的使用体验。本文将介绍一种方法,动态显示按钮状态,使其得到鼠标焦点后自动放大,失去鼠标焦点后自动缩小。   先放一张原图(鼠标还未移动到按钮上):   获得鼠标焦点的Button按钮:(这里因为是图

    2024年02月16日
    浏览(28)
  • element input组件自动失去焦点问题解决

    最近在 Vue3 + ElementPlus 中,使用 el-input 组件时,如果设置了 v-model ,那么在每次改变内容后后,input 会自动失去焦点,这样会导致用户无法输入多个字符。 如上图所示,配置项的 Name 和 Code 都是使用 el-input 组件 v-for 遍历渲染的,都绑定了 v-model ,而 :key 绑定的是对应的 Co

    2024年02月01日
    浏览(48)
  • el-input-number 失去焦点blur事件,

            最近遇到了个奇怪的需求,需要代码手动给数字输入框手动触发失焦事件;但是在看了 el-input-number 焦点事件部分的源码后,发现 el-input-number 只有获取焦点focus事件,却没有失去焦点的事件: 后来再阅读了 el-input-number的 template部分的源码后;发现el-input-number封装

    2024年02月13日
    浏览(30)
  • TextBox添加鼠标按下、失去焦点、鼠标移动等事件及重写

    TextBox添加鼠标按下、失去焦点、鼠标移动等事件及重写 方法1: 方法2:    

    2024年02月15日
    浏览(25)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包