实现“textare”点击输入字段

目录

一、组件textarea的template

二、页面page的template

1.显示且更改数据片段

2.使用组件注意案例

总结


例如:两个对象 :value1: { name: "字段1", value: 1 }, value2: { name: "字段2", value: 2 }

展示 "字段1"+"字段2",为(1+2) 运算"+"符号也是可以输入替换的


1.实现起来并不复杂这个vue中使用一个循环即可实现上图效果

    <span v-for="item in list" :key="item.id">
      {{ item.name }}
    </span>

2.输入框肯定是有是光标, 它是 一闪一闪的(显示隐藏)有规律的,这里就不必使用定时器了简单的动画css就可以实现:

.curcus-text {
  width: 1px;
  background-color: #0a89f3;
  display: block;
  animation: text 1s infinite;
  position: absolute;
  left: 7px;
  top: 8px;
}
@keyframes text {
  from {
    width: 1px;
  }
  to{
    width: 0;
  }
}

3.字段展示和光标都有了, 点击字段改变index位置, 用index改变光标位置

   /*methods*/
    setInterval(index) {
      this.intervalIndex = index;
    },

4.时候已经可以通过index更改指定数据了, 接下来就是一些dom的操作了,当index发生改变时对应光标位置也要发生改变,  将$refs.curcusText的left, 和top属性更改问点击的<span></span>的offsetLeft+offsetWidth, 和offssetTop  简单即可

    <span v-for="(item, index) in list" :key="item.id"  @click="setInterval(index)">
      {{ item.name }}
    </span>
    <span v-show="isCurcusText" class="curcus-text" ref="curcusText"></span>

5.或者是每个字段间隔都有一个光标currentIndex所在哪里则展示那个光标同样可以实现

    <span v-for="(item, index) in list" :key="item.id"  @click="setInterval(index)">
      {{ item.name }}
      <span v-show="index === currentIndex" class="curcus-text" ref="curcusText"></span>
    </span>

 6.我们还得考虑正常空字段数组为空的话$refs.curcusText的所在位置, 可以$refs.fromInterval来确定,正常情况下我们点击第二个字段光标是显示在第一个字段后面的, 这里用offsetLeft来确定位置还是offsetLeft+offsetWidth加上setInterval(index-1)都是可以是实现的

    <span ref="fromInterval"></span>
    <span v-for="(item, index) in list" :key="item.id"  @click="setInterval(index-1)">
      {{ item.name }}
    </span>
    <span v-show="isCurcusText" class="curcus-text" ref="curcusText"></span>
    /*watch*/
    //光标位置
    intervalIndex: {
      handler(index) {
        let { valueItems, fromInterval }= this;
        let x, y;
        if (~index && valueItems?.length && valueItems[index]?.offsetLeft) {
          let valueItem = valueItems[index];
          x = valueItem.offsetLeft + valueItem.offsetWidth;
          y = valueItem.offsetTop;
        } else {
          x = fromInterval?.offsetLeft;
          y = fromInterval?.offsetTop;
        }
        this.setCurcusTextIndex(x, y);
        this.$emit('update:index', index);
      },
      deep: true,
      immediate: true
    },
    /*methods*/
    //curcusText位置
    setCurcusTextIndex(x, y) {
      this.$nextTick(() => {
        if (this.$refs.curcusText) {
          this.$refs.curcusText.style.left = x +"px";
          this.$refs.curcusText.style.top = (y+1) +"px";
        }
      })
    },

 7.对于获取焦点和失去焦点这里我使用了监听document的点击事件来实现失去焦点, 和this.$el点击showCurcusText()获取焦点, 写在获取焦点document的点击中也是可以的,基本就这样就好了,具体代码看,组件textarea的template即可

    //显示光标
    showCurcusText(event) {
      let { valueItems } = this;
      let excludeDomList = valueItems;
      if (!~excludeDomList.findIndex(item => item === event.target)) {
        this.isCurcusText = true;
        let index = ~~valueItems?.length-1;
        this.intervalIndex = index;
      }
    },
    //document监听所有点击事件消除isCurcusText
    documentClikc() {
      document.addEventListener('click', (e) => {
        let excludeDomList = [].concat( this.valueItems, this.exclude);
        if (e.target !== this.$el && !~excludeDomList.findIndex(item => item === e.target)) {
          this.isCurcusText = false;
        }
      })
    },

一、组件textarea的template

示例:该组件是从textarea标签角度去封装的, 使用案例在页面page的template页面中

<template>
  <div class="textarea-box border-gray" @click="showCurcusText">
    <span ref="fromInterval"></span>
    <span v-for="(item, index) in valueArr" style="display:flex;" :key="index" ref="valueItem"
          :style="{ paddingLeft: paddingLeft ? paddingLeft[0]+'px' : 0,
          paddingRight: paddingLeft ? paddingLeft[1]+'px' : 0 }"
          @click="setInterval(index-1)">
      {{ item.name }}
    </span>
    <span v-show="isCurcusText" class="curcus-text" ref="curcusText"></span>
  </div>
</template>
 
<script>
 
export default {
  props: {
    valueList: Array, //数组型(值)
    isShow: Boolean, //光标 (代表焦点)
    index: Number, //光标所在索引
    exclude: { //点击dom不失去光标 dom数组
      type: Array,
      default: () => []
    },
    paddingLeft: { //值的左右padding[left, right]
      type: Array,
      default: () => [5, 1]
    }
  },
  data() {
    return {
      isCurcusText: false, //光标
      intervalIndex: -1, //光标索引 (-1~length)
      fromInterval: null, //光标起始位置dom
      valueArr: [],
      arrStore: [],
      valueItems: [],
    };
  },
  watch: {
    valueList: {
      handler(val) {
        this.valueArr = val;
      },
      deep: true,
      immediate: true
    },
    valueArr: {
      handler(val) {
        this.$emit('update:valueList', val);
        this.getExcludeDom(() => {
          let len = val.length - this.arrStore.length;
          this.arrStore = JSON.parse(JSON.stringify(val));
          if (this.intervalIndex < val.length) {
            if (this.valueItems?.length) {
              this.intervalIndex += len > 0 ? len : 0;
            }
          }
        });
      },
      deep: true,
      immediate: true
    },
    isShow(val) {
      this.isCurcusText = val;
    },
    isCurcusText(val) {
      this.$emit('update:isShow', val);
    },
    //光标位置
    intervalIndex: {
      handler(index) {
        let { valueItems, fromInterval }= this;
        let x, y;
        if (~index && valueItems?.length && valueItems[index]?.offsetLeft) {
          let valueItem = valueItems[index];
          x = valueItem.offsetLeft + valueItem.offsetWidth;
          y = valueItem.offsetTop;
        } else {
          x = fromInterval?.offsetLeft;
          y = fromInterval?.offsetTop;
        }
        this.setCurcusTextIndex(x, y);
        this.$emit('update:index', index);
      },
      deep: true,
      immediate: true
    },
    index(i) {
      this.intervalIndex = i;
    }
  },
  methods: {
    //curcusText位置
    setCurcusTextIndex(x, y) {
      this.$nextTick(() => {
        if (this.$refs.curcusText) {
          this.$refs.curcusText.style.left = x +"px";
          this.$refs.curcusText.style.top = (y+1) +"px";
        }
      })
    },
    //点击间隙
    setInterval(index) {
      this.isCurcusText = true;
      this.intervalIndex = index;
    },
    //显示光标
    showCurcusText(event) {
      let { valueItems } = this;
      if (!~valueItems.findIndex(item => item === event.target)) {
        this.isCurcusText = true;
        let index = ~~valueItems?.length-1;
        this.intervalIndex = index;
        console.log(valueItems)
      }
    },
    //删除valueItem
    deleteIntervalBtn() {
      if (~this.intervalIndex) {
        this.valueArr.splice(this.intervalIndex, 1);
        this.intervalIndex--;
      }
    },
    //光标左右移动
    setIntervalIndex(state) {
      if (state) {
        let index = this.intervalIndex+1;
        let maxIndex = this.valueArr.length-1;
        this.intervalIndex = index > maxIndex ? maxIndex : index;
      }else {
        let index = this.intervalIndex-1;
        let minIndex = -1;
        this.intervalIndex = index < minIndex ? minIndex : index;
      }
    },
    //获取不包含dom
    getExcludeDom(fuc) {
      this.$nextTick(() => {
        this.valueItems = this.$refs.valueItem;
        fuc();
      })
    },
    //document监听所有点击事件消除isCurcusText
    documentClikc() {
      document.addEventListener('click', (e) => {
        let excludeDomList = [].concat( this.valueItems, this.exclude);
        if (e.target !== this.$el && !~excludeDomList.findIndex(item => item === e.target)) {
          this.isCurcusText = false;
        }
      })
    },
    //document监听删除和左右键
    documentKey() {
      document.addEventListener('keydown', (e) => {
        let k= e.keyCode;
        if (!this.isCurcusText || !this.valueArr.length) return null;
        switch (k) {
          case 8: //backspace删除
            this.deleteIntervalBtn();
            break;
          case 46: //delete删除
            this.deleteIntervalBtn();
            break;
          case 37: //左
            this.setIntervalIndex(0);
            break;
          case 39: //右
            this.setIntervalIndex(1);
            break;
        }
      })
    },
    //获取光标起始位置
    getFromIntervalDom() {
      this.fromInterval = this.$refs.fromInterval;
    }
  },
  mounted() {
    this.$nextTick(() => {
      this.getFromIntervalDom();
      this.documentClikc();
      this.documentKey();
    })
  }
}
</script>
 
<style scoped>
.textarea-box {
  width: 100% !important;
  height: 150px;
  color: #707070;
  padding: 7px;
}
.border-gray {
  border: 1px solid rgb(225, 225, 225);
  overflow: hidden scroll;
  cursor: text;
  position: relative;
}
.border-gray::-webkit-scrollbar {
  display: none;
}
.border-gray span {
  height: 20px;
  display: block;
  float: left;
}
.interval-btn {
  width: 5px;
}
.curcus-text {
  width: 1px;
  background-color: #0a89f3;
  display: block;
  animation: text 1s infinite;
  position: absolute;
  left: 7px;
  top: 8px;
}
@keyframes text {
  from {
    width: 1px;
  }
  to{
    width: 0;
  }
}
</style>

二、页面page的template

这里我  import pushTextarea from "@/components/common/input/pushTextarea"; 导入封装好的pushTextarea组件 复制是请注意自己的文件路径

<template>
  <div class="content-box" ref="boxDom">
    <div class="calculator-box">
      <div class="calculator-input-warp">
        <div class="form-item-box">
          <span class="form-item-box-title">计算公式:</span>
          <push-textarea ref="computationalPushTextarea"
                        v-model:is-show="isShow" v-model:value-list="valueList" v-model:index="index" :exclude="btnItemDomList"/>   
        </div>
        <div class="btn-item" @click="consoleValue()" style="width: 100px;">
          输出
        </div>    
      </div>
      <div class="calculator-btn-warp">
        <div class="btn-warp-box">
          <div class="btn-warp">
            <div v-for="item in btnList" :key="item.id" class="btn-warp-item" ref="btnItem">
              <div class="btn-item" ref="btnItem"
                   @click="pushList(item)">
                {{ item.name }}
              </div>
            </div>
          </div>
          <div class="btn-warp" style="margin-top: 20px;">
            <div v-for="item in paramsBtn" :key="item.id" class="btn-warp-item" ref="btnItem">
              <div class="btn-item" ref="btnItem"
                   @click="pushList(item)">
                {{ item.name }}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
 
<script>
 
import pushTextarea from "@/components/common/input/pushTextarea";
 
export default {
  components: {
    pushTextarea
  },
  data() {
    return {
      btnItemDomList: [],
      index: -1,
      isShow: false,
      valueList: [{id: 1, name: "test"}],
      btnList:[
        { id:'+' ,name: '+'}, { id:'-' ,name: '-'}, { id: '*', name: "*" }, { id: '/', name: '/' }, { id: '(', name: "(" }, { id: ')', name: ")" },
        { id:'=' ,name: '='}, { id:'>' ,name: '>'}, { id: '>=', name: ">=" }, { id: '<', name: '<' }, { id: '<=', name: "<=" }, { id: '<>', name: "<>" },
        { id:'并且' ,name: '并且'}, { id:'或者' ,name: '或者'}, { id: 'case', name: "case" }, { id: '如果', name: '如果' }, { id: '则', name: "则" },
        { id: '否则', name: "否则" }, { id: 'end', name: "end" }, { id: 46, name: "删除" },
      ],
      paramsBtn: [
        { id: 1, name: "字段1", value: 7 },
        { id: 2, name: "字段2", value: 16 },
        { id: 3, name: "字段3", value: 8 },
      ]
    };
  },
  methods: {
    //获取字段
    consoleValue() {
      console.log(this.valueList);
      this.valueList = [{id: 2, name: "testtesttest"}];
    },
    //添加运算字段
    pushList(text) {
      if (this.isShow){
        if (text.id !== 46) {
          this.valueList.splice(this.index+1, 0, text);
        }else { //删除
          this.$refs?.computationalPushTextarea && this.$refs.computationalPushTextarea?.deleteIntervalBtn();
        }
      }
    },
    //获取btn-item 元素列表
    getBtnItemDomList() {
      this.$nextTick(() => {
        this.btnItemDomList = this.$refs.btnItem;
      })
    },
    //清除pushTextarte缓存
    clearTextarea() {
      this.index = -1;
      this.isShow = false;
      this.valueList = [];
      this.getBtnItemDomList();
    }
  },
  mounted() {
    this.getBtnItemDomList();
  }
};
</script>
 
<style scoped>
.content-box {
  margin-top: 50px;
  height: 100%;
  width: 100%;
  padding: 0 20px;
  overflow: hidden scroll;
}
.foot-box {
  right: 0;
  bottom: 0;
  width: 100%;
  border-top: 1px solid #e9e9e9;
  padding: 10px 16px;
  background: #fff;
  text-align: right;
  z-index: 1;
}
.flex-box {
  display: flex;
}
.flex-box > div {
  flex: 1;
}
.calculator-box {
  display: flex;
}
.calculator-box div {
  flex: 1;
}
.form-item-box {
  display: flex;
  margin-bottom: 20px;
}
.form-item-box-title {
  margin-right: 10px;
  width: 80px;
  text-align: right;
}
.calculator-btn-warp {
  height: auto;
}
.btn-warp-title {
  color: red;
}
.btn-warp {
  position: relative;
  display: flex;
  flex-wrap: wrap;
}
.btn-warp-item {
  min-width: 20%;
  padding: 5px;
  position: relative;
  box-sizing: content-box;
}
.btn-item {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 5px 10px;
  color: #1295f2;
  border-radius: 5px;
  border: 1px solid #1295f2;
  cursor: pointer;
}
</style>

1.显示且更改数据片段

代码如下(示例):

//添加运算字段
pushList(text) {
  if (this.isShow){
    if (text !== 46) {
      this.valueList.splice(this.index+1, 0, text);
    }else { //删除
      this.$refs?.computationalPushTextarea && this.$refs.computationalPushTextarea?.deleteIntervalBtn();
    }
  }
},

此函数调用的添加删除数组操作,组件双向绑定了index, isShow(两个来控制是状态和位置), valueList双向绑定的数组,操作他则页面对此更改。由于点击其他元素我们因该让isShow=false, document.addEventListener()监听document点击来操作“isShow=false”,按钮我们是不需要操作“isShow=false”的所以对此btnItemDomList这个参数就是传递点击不执行”isShow=false“的DOM列表。

2.使用组件注意案例

1.每次btnList更改后btnItem则已经改变对此exclude也需要发生改变则调用getBtnItemDomList()函数重新获取btnItem, 还有每次valueList重新赋值(不指更改数组内元素). 当valueList  =  [{id: 1, name: "test"}]时候点击this.$el光标位置显示最后一个字段后, 又改变为valueList = [{id: 2, name: "testtesttest"}],再次点击this.$el光标位置本因该显示最后一个字段后


 可光标位置并没有改变这是因为我们改变位置是根据监听intervalIndex发生改变而重新获取光标位置, 而数据valueArr长度改变则intervalIndex发生改变,实则valueArr发生了变化但长度并没有改变只不过字段改变了,由于字段长度更长(短也一样)了字段后的位置因该是光标所位置, 但并不一致了,这是后我么们如果要改变valueArr则希望先调用clearTextarea()函数把数据重新初始化.在进行赋值则将问题解决了. 如果使用最前面说的每个字段后(或前)都加上光标对应展示则不会有些这问题, 且很多地方都很更加简单.

总结

提示:这里对文章进行总结:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值