mpvue icon indicating copy to clipboard operation
mpvue copied to clipboard

input光标闪烁

Open youngluo opened this issue 7 years ago • 43 comments

在input基础上自定义组件,实现v-model,输入框光标闪烁

示例

youngluo avatar Apr 08 '18 08:04 youngluo

感觉不是单纯的 v-model 问题,可否提供下 demo

anchengjian avatar Apr 08 '18 15:04 anchengjian

@anchengjian 示例

image

youngluo avatar Apr 08 '18 15:04 youngluo

同样的问题,因为v-mode是通过bindinput事件实现的

rchunping avatar Apr 09 '18 05:04 rchunping

@rchunping 之前觉得也是这个原因,但是我在onSearchChange里不赋值就不会闪烁

youngluo avatar Apr 09 '18 05:04 youngluo

@youngluo 按照bindinput的说明,事件处理函数中如果返回了字符串,那么会替换input中的内容,我猜问题出在这里?

rchunping avatar Apr 09 '18 06:04 rchunping

@rchunping 请问这个问题解决了吗?

xyt0110 avatar Apr 13 '18 03:04 xyt0110

@xiaoyang3874 不用v-mode , 自己 @input , 在input事件里更新

<input :value="value" @input="onInput" />
data() {
  return {
      value:''
  }
}
methods: {
     onInput (e) {
          this.value  = e.mp.detail.value
     }
} 

rchunping avatar Apr 13 '18 03:04 rchunping

@rchunping 这样的话,当你进行输入数据修改的时候,比如输入了一串数字,修改中间几个,当点击第一次删除的时候,光标会自动移动到最后,同理,在插入的时候,刚输入一个字的时候,光标也会移动到最后,变成了除了第一个,其他的都在尾部删除或追加了

sharkdong avatar Apr 14 '18 09:04 sharkdong

@sharkdong 是的,安卓正常,iOS上会有你说的情况

rchunping avatar Apr 14 '18 12:04 rchunping

@rchunping 那有什么好的解决方案吗?这边比较头疼,看到美团官方提供的 美团汽车票的小程序,在搜索框输入的时候,也出现这个问题,用户体验特别不好,现在用户要我们改,唉😔

sharkdong avatar Apr 14 '18 12:04 sharkdong

@sharkdog 那不用实时获取输入内容啊,@confirm 或者表单@submit 都可以拿到值

rchunping avatar Apr 14 '18 13:04 rchunping

same issue

simpledyt avatar Apr 16 '18 03:04 simpledyt

使用 lazy 修饰符绑定应该不会有这个问题吧 v-model.lazy="text"

F-loat avatar Apr 16 '18 11:04 F-loat

@F-loat 如果有v-model.lazy的话,会出现另外一个问题,因为lazy修饰符是触发change事件,而mpvue会将change事件变成blur事件,所以就会导致,如果填写好表单,并没有移开光标,直接点击提交,该字段会没有提交到后台,因为blur事件还没有触发,所以数据还没有绑定

sharkdong avatar Apr 16 '18 11:04 sharkdong

@sharkdong 我这边开发者工具中,输入后直接点击 button 提交,获取到的值是没问题的,真机暂时还没测试

F-loat avatar Apr 16 '18 12:04 F-loat

@sharkdong 我这边是利用input的回调来更新绑定的变量的值的,input上不绑定value,当表单提交后设置一个isClear状态置为true,使用假的input项展示默认的提示。

伪代码如下:

<view>
    <view 
      @click="handleResetClearInput"
      v-if="!cmtInput.focus && cmtInput.isClear"
      class="fake-input">{{ cmtInputPlaceholder }}</view>
    <input
      v-if="!cmtInput.isClear"
      :focus="cmtInput.focus"
      :placeholder="cmtInputPlaceholder"
      type="text"
      @input="onCmtInput"
      @confirm="onCmtInputSubmit"
      @click="cmtInput.focus = true"
    />
    <i class="icon-send" :class="{ 'is-disabled': cmtInput.content.length === 0 }" @click="onCmtInputSubmit"></i>
</view>
data () {
    return {
        cmtInput: {
            isClear: true,
            content: '',
            focus: false
        }
    }
},

methods: {
    handleResetClearInput () {
        this.cmtInput.focus = true
        this.cmtInput.isClear = false
        this.cmtInput.content = ''
    },
    onCmtInput (e) {
        this.cmtInput.content = e.mp.detail.value
    },
    onCmtInputSubmit () {
        this.cmtInput.focus = false
        // 发布
        publish().then(() => {
            this.cmtInput.focus = false
            this.cmtInput.isClear = true
            this.cmtInput.content = ''
        })
    }
}

isunkui avatar Apr 16 '18 12:04 isunkui

@F-loat 安卓机没问题的,但是那个苹果机子会出现这样的问题,我测试过,包括美团自己的小程序,美团汽车票都会出现这样的问题

sharkdong avatar Apr 16 '18 12:04 sharkdong

@isunkui 你这样的话,也可以,但是如果初始值需要的话,你怎么填充,比如刚进来,输入框的初始值是从后台拿到的,需要双向绑定,还是躲不掉用value或者v-model,还是会出现这样的问题

sharkdong avatar Apr 16 '18 12:04 sharkdong

@sharkdong 如果需要用到双向绑定确实没找到很好的解决方案,目前我们的产品设计上不存在这个问题。

isunkui avatar Apr 16 '18 12:04 isunkui

@isunkui 这样的方法也考虑过,可能页面用到的内容比较多,所以就想了想放弃了,不过仔细想一下,只要存在双向绑定,应该都会出现这个问题,在input和textarea上。后来针对这个单页面,使用原生吧,可能这样好点

sharkdong avatar Apr 16 '18 12:04 sharkdong

是的 望优化一下吧

lanshengzhong avatar Apr 18 '18 04:04 lanshengzhong

因为在播放音频 使用了vuex一秒会commit一次拿去实时时间 然后input会出现莫名其妙的问题 test 一直在输入中。。。

lanshengzhong avatar Apr 18 '18 09:04 lanshengzhong

@youngluo @F-loat 遇到这个问题, 调了一个晚上, 弄了个workaround:

const vModelBugfillMixin = {
  data() {
    return {
      ownValue: null,
    };
  },
  model: {
    prop: 'valueFromProp',
  },
  props: {
    valueFromProp: {
      type: String,
    },
  },
  computed: {
    value() {
      return this.ownValue == null ? this.valueFromProp : this.ownValue;
    },
  },
  methods: {
    onChange(event) {
      this.ownValue = event.target.value;
      setTimeout(() => this.$emit('input', event.target.value));
    },
  },
};

export default vModelBugfillMixin;

首先思路来自这个comment, 根据这个一路在 mpvue 源码里面 找到了 updateDataToMP方法,

  function updateDataToMP () {
    var page = getPage(this);
    if (!page) {
      return
    }

    var data = formatVmData(this);
    console.log('updateDataToMP', data);
    throttleSetData(page.setData.bind(page), data);
  }

打了log之后发现, 每次更新的时候, 输出结果如下: image

点开看了一下, 每次对一个组件进行更新的时候 其实包括其children都进行了更新, 所以闪烁的原因很简单了:

考虑一个3层的双向数据流: page.value <=> component.props.value <=> input.value

每当用户输入时, 按顺序发生以下时间:

  1. input.value 先变了 (界面上有反应, 比如 1 变成了 12)
  2. input 元素触发了 'input' 事件
  3. component 拦截并继续传播 'input' 事件 的 e.target.value
  4. page.value 发生了改变, 事件结束 中间再发生些什么...
  5. page 用小程序的 setData 接口更新数据, 也就是日志的第一行$root.0 (界面上 12 变回了 1)
  6. component 用小程序的 setData 接口更新数据, 也就是日志的第一行$root.0,0 (界面上 1 又变成了 12)
  7. input 用小程序的 setData 接口更新数据

闪烁的关键就出现在第5步, 因为这个时候component.props.value 还是旧的值, 但page对vmData的更新包括了其children的vmData

解决方案就一目了然了:

  1. 让第 1 步不变, 我们除非我们用一个假的input罩在用户实际输入的input上面, 让假的input最后更新
  2. 让第 5 步不变, 修改mpvue源码, 不要更新children, 但显然牵扯更多,
  3. 让第 5 步和第 6 步调换顺序, 先把component.value更新了, 就不怕page的更新了

最终使用的是方案3, 这样的代价是2次更新变成了3次更新, 如果一共有n层的话 就是 n 变成 (2n - 1)

image

Stupidism avatar May 12 '18 17:05 Stupidism

我在component中,使用computed赋到input的v-model上, get为先取组件内部raw,取不到则使用value set时,会触发一个方法提交数据到vuex。

            editing: {
                get() {
                    return this.raw || String(this.value)
                },
                set(val) {
                    this.raw = val
                    this.setFieldVal({
                        fieldName: this.fieldKey,
                        value: val
                    })
                }
            },

参考 @Stupidism 的分析,添加nextTick,确保先触发子组件的,然后触发父组件的setData 效果是,闪烁减少了。但依然有一点儿。使用中文输入法的时候,输入太快就有影响。

            editing: {
                get() {
                    return this.raw || String(this.value)
                },
                set(val) {
                    this.raw = val
                    this.$nextTick(()=>{
                        // 延迟提交vuex, 确保先更新children,减少mpvue闪烁
                        this.setFieldVal({
                            fieldName: this.fieldKey,
                            value: val
                        })
                    })
                }
            },

songlairui avatar Jun 05 '18 09:06 songlairui

使用了lazy修饰符导致不能实时更新了

cduyzh avatar Jun 09 '18 08:06 cduyzh

不用组件,问题解决!

lanten avatar Jul 10 '18 05:07 lanten

不用组件 还真有点用。。。。

qinouz avatar Jul 12 '18 04:07 qinouz

抱歉我不会复制代码....

Stupidism avatar Jul 13 '18 02:07 Stupidism

@sharkdong @isunkui @youngluo 一个除了不支持赋值给input,其它问题都解决了的hack方案。

  • 不给input绑定value,使用其它变量记录input的值;
  • 使用v-if指令重置节点,模拟清空input。

ezgif-5-39c9fb0ecb

<template lang="html">
  <div class="search-input">
    <img :src="searchIcon" class="search-icon" />
    <!-- 因mpvue的bug(http://mci.sankuai.com/osx/travel-with-idol/issues/19),帮能绑定value -->
    <input
      v-if="showInput"
      type="text"
      @input="handleInput"
      confirm-type="search"
      class="input"
      :placeholder="placeholder"
      placeholder-style="color: #999; font-size: 30rpx"
    >
    <img
      v-if="_value"
      :src="clearIcon"
      @click="handleClear"
      class="clear-icon"
    />
  </div>
</template>

<script>
import searchIcon from '@/common/assets/search.svg'
import clearIcon from '@/common/assets/clear.svg'

export default {
  name: 'search-input',
  props: {
    placeholder: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      _value: '',
      searchIcon,
      clearIcon,
      showInput: true,
    }
  },
  methods: {
    handleInput(evt) {
      this._value = evt.target.value
      this.$emit('input', evt.target.value)
    },
    handleClear() {
      this.$emit('input', '')
      this._value = ''
      // 未绑定input value的下,模拟清空value
      this.showInput = false
      setTimeout(() => {
        this.showInput = true
      }, 10)
    }
  },
}
</script>

hughfenghen avatar Jul 23 '18 03:07 hughfenghen

mark

netbuffer avatar Jul 27 '18 14:07 netbuffer