How to create the perfect autosize textarea? - vue.js

I do auto expanding textarea.
The principle is this: I create a hidden div in which I place the input text, then in the updated () method I define the height of the div and apply the value to the textarea.
But there is one problem - there is text twitching, because First, the text crawls up, and then when the field is expanded, it returns to its place. As if the updated () method works late. By the same principle, I made a text field in the ReactJS there was no such effect.
What can do with it?
How it works: https://jsbin.com/zakavehewa/1/edit?html,css,js,console,output
<template>
<div class="textarea_wrap">
<textarea class="text_input textarea" v-model="value" ref="textarea"></textarea>
<div v-if="autoRow" class="text_input textarea shadow" ref="shadow">{{ value }}!</div>
</div>
</template>
<script>
export default {
props: {
autoRow: {
type: Boolean,
default: false
},
default: String
},
data () {
return {
value: this.default,
}
},
mounted() {
this.updateHeight()
},
updated() {
this.updateHeight()
},
methods: {
updateHeight() {
if (this.autoRow && this.$refs.shadow) {
this.$refs.textarea.style.height = this.$refs.shadow.clientHeight + 5 + 'px'
}
}
}
}
</script>
<style lang="scss" scoped>
.textarea_wrap {
position: relative;
}
.textarea {
line-height: 1.5;
min-height: 31px;
width: 100%;
font-family: inherit;
}
.shadow {
position: absolute;
left: -9999px;
pointer-events: none;
white-space: pre-wrap;
word-wrap: break-word;
resize: none;
}
</style>

Checkout https://github.com/wrabit/vue-textarea-autogrow-directive, it caters for rendering in hidden divs plus copying and pasting text.

Related

how to set a trey of letters`style differently in a v-for loop?

It is similar to the question of whose URL I`ll put below but with one difference, I want not one letter or word style conditionally presented but a trey of letters, I wish all the letters to be in
a different color, is there any solution to it?
the tricky thing is the v-for loop based on an array whose length is not constant.
Change the style of a substring in a v-for loop
example:
I want this input tag letters full of different random colored letters
here is the full code:
<template>
<div
class="nice-input"
:class="{'nice-input--shaked': animated, 'nice-input--caret': !caret}"
>
<input
:id="id !== undefined ? id : 'input-'+name"
v-model="value"
type="text"
:name="name"
autocomplete="off"
>
<label :for="id !== undefined ? id : 'input-'+name">
<span v-for="word in arr" :key="word.sa" class="nice-input__animate"
:style="randomColor">{{ word }}</span>
</label>
</div>
</template>
<script>
export default {
name: 'weirdInput',
props: ['name', 'id'],
data () {
return {
animated: false,
caret: true,
value: '',
randomColor: '#000000',
}
},
computed: {
arr: function () {
return this.value.split('')
},
},
watch: {
value: {
handler: function (after, before) {
const self = this
this.caret = false
if (after.length > before.length) {
if (this.value.slice(-1) === ' ') return false
setTimeout(function () {
self.animated = true
setTimeout(function () {
self.animated = false
self.caret = true
}, 300)
}, 750)
}
this.randomColor = 'color: #' + Math.floor(Math.random() * 16777215).toString(16)
},
},
},
}
</script>
<style scoped>
.nice-input {
position: relative;
}
.nice-input input {
border:none;
border-radius:4px;
padding:7px 10px;
font-family: 'Lato', sans-serif;
font-size:14px;
box-shadow: rgba(0,0,0,.05) 0 5px 20px;
letter-spacing:0;
width:1000px;
color: transparent;
font-weight:900;
caret-color: #555;
}
.nice-input input:focus {
outline:none;
box-shadow: rgba(0,0,0,.1) 0 5px 20px;
}
.nice-input label {
position: absolute;
top: 6px;
left: 10px;
letter-spacing:0;
font-size:0;
}
.nice-input span {
font-family: 'Lato', sans-serif;
font-size:14px;
font-weight:900;
}
.nice-input__animate {
animation: print .75s 1 ease-in-out;
}
.nice-input--shaked {
animation: shake .2s 1 ease-in-out;
}
.nice-input--caret {
caret-color: transparent;
}
#keyframes print {
from{
position:absolute;
transform: scale(100);
}
99% {
position:absolute;
}
to {
position:relative;
}
}
#keyframes shake {
from,
to {
}
50% {
transform:scale(0.97);
}
}
</style>

Vue onclick display specific item

I have a question about Vue.
I want to add a class to a specific item:
<p v-on:click="display = !display">Rediger joke</p>
Display is False before and it change it to true.
And it works. But my problem is, that this onclick is inside an v-for loop, and i only want to put "display" on one "update-site" and not all of them. Can i do this or do I have to try a different setup?
Thanks a lot
I have this idea that might help you. The idea is you extend post object with for example visible property and when you click event triggered you change this property and add .display class. Please check this jsfiddle
template
<div id="app">
<article v-for="post in filteredPosts" :key="post.id">
{{post.name}}
<button #click="display(post)">show</button>
<div class="post-content" :class="{display: post.visible}">this is the part I want to display onclick</div>
<hr />
</article>
</div>
css
.post-content {
display: none;
}
.post-content.display {
display: block;
}
code
new Vue({
el: '#app',
data() {
return {
posts: []
};
},
created() {
//when you load posts. add visible property.
setTimeout(() => {
//posts from server
var postsFromServer = [{
id: 1,
name: 'Post One'
},
{
id: 2,
name: 'Post Two'
}
];
//add visible proprty.
this.posts = postsFromServer.map(post => {
return {
...post,
visible: false
};
});
}, 1000);
},
computed: {
filteredPosts() {
//do your filters
return this.posts;
}
},
methods: {
display(post) {
this.$set(post, 'visible', !post.visible);
}
}
});
I have an article, and i get the data from Firebase.
<article v-for="post in filteredPosts" :key="post.id">
{{post.name}}
<p v-on:click="display = !display"></p>
<div>this is the part I want to display onclick</div
</article>
updateInputs has display:none, but onclick I want it to be display as block:
.updateInputs.display {
display: block;
position: absolute;
top:0;
left:0;
bottom: 0;
background-color: white;
box-shadow: 4px 4px 10px black;
width: 100%;
height: auto;
overflow: hidden;
overflow-y: scroll;
padding-bottom: 10px;
}

Vue-multiselect - How to insert html code in placeholder?

Im trying insert span in placeholder, for color change. But placeholder returns only string, ow to fix that?
computed: {
customPlaceholder () {
let numLength = this.options.length;
return this.placeholder + "<span>"+numLength+"</span>"
}
}
I think you're trying to add a custom placeholder inside an input field.
to do this you need some mix of css and html.
new Vue({
el: '#editor',
data: {
input: '',
input2: 'some text'
},
computed: {
placeholderText: function () {
return `${this.input2} <span>*</span>`
}
},
methods: {
update: _.debounce(function (e) {
this.input = e.target.value
}, 300)
}
})
#editor div {
display: inline-block;
}
.input-placeholder {
position: relative;
}
.input-placeholder input {
padding: 10px;
font-size: 25px;
}
.input-placeholder input:valid + .placeholder {
display: none;
}
.placeholder {
position: absolute;
pointer-events: none;
top: 0;
bottom: 0;
height: 25px;
font-size: 25px;
left: 10px;
margin: auto;
color: #ccc;
}
.placeholder span {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/lodash#4.16.0"></script>
<div id="editor">
<div class="input-placeholder">
<input type="text" #input="update">
<div class="placeholder" v-if="!input" v-html="placeholderText">
Email <span>*</span>
</div>
</div>
</div>
I have created this jsfiddle for my solution.
you can use css placeholder selector
input::-webkit-input-placeholder { /* Edge */
color: green!important;
}
input:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: green!important;
}
input::placeholder {
color: green!important;
}
Try it, then try to remove the !important.
But information is missing. You want to change the color dynamically or not? or you want to have different colours into the same placeholder?

How can I bind the animation duration in Vue.js?

What I am trying to build (and not use an existing solution) is an indeterminate loader with the following template:
<template>
<div class="slider">
<div class="line" :style="getLineStyle"></div>
<div class="subline inc"></div>
<div class="subline dec"></div>
</div>
</template>
I then use a getter to add the styles for the div with the line class (which works fine).
#Prop({ default: "hsl(0, 0%, 90%)" }) private backColor!: string;
...
public get getLineStyle(): any {
return {
"background-color": "black",
position: "absolute",
opacity: "0.4",
background: this.backColor,
width: "150%",
height: "100%"
};
}
I also have the following CSS:
<style lang="scss" scoped>
.slider {
position: relative;
height: 2px;
overflow-x: hidden;
}
.subline {
position: absolute;
background: #4a8df8;
height: 100%;
}
.inc {
animation: increase 2s infinite;
}
.dec {
animation: decrease 2s 0.5s infinite;
}
#keyframes increase {
from {
left: -5%;
width: 5%;
}
to {
left: 130%;
width: 100%;
}
}
#keyframes decrease {
from {
left: -80%;
width: 80%;
}
to {
left: 110%;
width: 10%;
}
}
</style>
What I want to do is turn the .inc and .dec classes to property getters as well so that I can bind the animation duration (currently set to 2s) to a property.
I initially tried modifying the template to:
<template>
<div class="slider">
<div class="line" :style="getLineStyle"></div>
<div class="subline inc" :style="getAnimateIncreaseStyle"></div>
<div class="subline dec" :style="getAnimateDecreaseStyle"></div>
</div>
</template>
With the following getters:
public get getAnimateIncreaseStyle() {
return {
animation: "increase 2s infinite"
};
}
public get getAnimateDecreaseStyle() {
return {
animation: "decrease 2s 0.5s infinite"
};
}
Only to realise that animations cannot work when added inline.
I cannot think of any other way of doing this. Any ideas?
This is how i bind the animation duration on my progress bar in vue 3, as the previous response. It's necessary removes the scope on style
<template>
...
<div v-if="duration > 0" class="w-full bg-gray-200 -mb-1">
<div
:class="`progress-bar h-0.5 progress-bar-${themeColor}`"
:style="animation"
></div>
</div>
...
</template>
<script lang="ts">
...
props: {
duration: {
type: Number,
default: 10,
},
themeColor: {
type: String,
required: false,
default: "blue",
},
},
computed: {
animation(): string {
return `animation: ${this.duration}s linear theme-color, ${this.duration}s ease-out enter forwards`;
},
},
...
<script>
<style lang="scss">
.progress-bar {
#apply bg-gray-300;
transform-origin: right;
&.progress-bar-blue {
#apply bg-gradient-to-r from-blue-600 to-blue-300;
}
&.progress-bar-green {
#apply bg-gradient-to-r from-green-600 to-green-300;
}
&.progress-bar-yellow {
#apply bg-gradient-to-r from-yellow-600 to-yellow-300;
}
&.progress-bar-red {
#apply bg-gradient-to-r from-red-500 to-red-300;
}
}
#keyframes theme-color {
100% {
background-position: 0%;
}
0% {
background-position: 100%;
}
}
#keyframes enter {
100% {
transform: scaleX(0);
}
0% {
transform: scaleX(1);
}
}
</style>

How to do databind two way in v-html?

I have an element div with atribute contenteditable="true". This div behaves like an element textarea.
<div v-on:keyup.enter="SendMensage" v-html="msg" contenteditable="true"></div>
my code:
data() {
return {
msg: '',
}
},
methods: {
enviaMensagem() {
console.log(this.msg);
}
}
My problem is that the databind does not work. What is typed in the div does not reflect on the variable. Does anyone know what can it be?
You need to listen to changes of the element, because v-model only works on <textarea> or <input>. You can do this by using an #input listener.
The markup you get like this will be escaped. If you want to unescape it, you can use e.g. this approach. Then, you actually have the markup right next to the pseudo-textarea field. So, why not using a <textarea> from begin with?
new Vue({
el: '#editor',
data: {
msg: ''
},
methods: {
typing: function(el) {
this.msg = el.target.innerHTML;
},
submit: function() {
console.log("Submitting: ", this.msg);
}
}
});
.input {
display: inline-block;
vertical-align: middle;
border: 1px solid black;
width: 200px;
height: 100px;
}
.output {
display: inline-block;
vertical-align: middle;
width: 200px;
height: 100px;
background: #eee;
}
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
<div id="editor">
<div class="input" #input="typing" #keyup.enter="submit" contenteditable="true"></div>
<div class="output" v-html="msg"></div>
</div>