I would like to split the active items to 50% height of their parent element. So when I open the first two items, they should split to 50% of their parent class .items (both .item have 100px). So I can see both without scrolling. Also when I open all three of them, they should get the height of 100px, which is the half of their parent. My problem is, tha the second item overlaps and I have to scroll. Whats wrong?
angular.module("myApp", []).controller("myController", function($scope) {
$scope.itemList = [{
id: 1,
name: "Item 1",
isOpen: false
}, {
id: 2,
name: "Item 2",
isOpen: false
}, {
id: 3,
name: "Item 3",
isOpen: false
}];
$scope.setHeight = function() {
if ($scope.itemList.length > 1) {
var typeHeaderHeight = $('.item-header').outerHeight();
var halfHeight = Math.round($('.items').outerHeight() / 2);
setTimeout(() => {
$('.item').css('height', typeHeaderHeight);
$('.item.active').css('height', halfHeight);
});
}
}
});
.frame {
display: flex;
flex-direction: column;
height: 200px;
}
.items {
display: flex;
flex: 1 1 auto;
flex-direction: column;
overflow: auto;
border: 1px solid black;
}
.item {
display: flex;
flex-direction: column;
flex: 0 0 auto;
margin-bottom: 5px;
background-color: rgb(150, 150, 150);
color: white;
}
.item:hover {
cursor: pointer;
}
.item-header {
position: relative;
display: flex;
}
.active {
background-color: rgb(220, 220, 220);
color: rgb(150, 150, 150);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="frame" ng-app="myApp" ng-controller="myController">
<div class="items">
<div class="item" ng-repeat="item in itemList" ng-click="item.isOpen = !item.isOpen; setHeight()" ng-class="{active: item.isOpen}">
<div class="item-header">
{{item.name}}
</div>
</div>
</div>
</div>
The issue is that you are setting the values of typeHeaderHeight and halfHeight variables outside the timeout so it always calculated before the item is actually opened so there is mistake in the calculations
try to make it like
setTimeout(() => {
var typeHeaderHeight = $('.item-header').outerHeight();
var halfHeight = Math.round($('.items').outerHeight() / 2);
$('.item').css('height', typeHeaderHeight);
$('.item.active').css('height', halfHeight);
});
},500)
One more thing .. I don't recommend to use setTimeout of vanilla JavaScript try to use $timeout instead of it
Related
I want a feature on my Navbar where if my Navbar's width is greater than its parent div, fade the very right of it and add a new button to it. Something similar to Youtube.
1- When the width of Navbar is greater than its parents div it looks like this:
2- When the width of Navbar is less than its parents div it looks like this:
How can I achieve this?
Also this is my Navbar:
<template>
<nav class="navbar navbar-expand-lg navbar-light bg-light pl-4">
<form class="form-inline" v-for="(genre, index) in genres" :key="index">
<button class="btn btn-outline-dark m-1" type="button">
{{ genre }}
</button>
</form>
</nav>
</template>
<script>
export default {
data() {
return {
genres: [
.
.
.
"REALITY TV",
"SPORTS",
"HORROR"
]
};
}
};
</script>
So, what you need to do (also what I think youtube is doing):
Have a container(div) to keep all the buttons in them, with overflow hidden, to disallow scroll. Have a parent div to house the container div.
Have two absolutely positioned arrow buttons on left and right of the buttons container.
Scroll the container manually using css transform: translateX(0px).
Change the value of px to translate on click of those arrow buttons.
Calculate the size of the container div, and parent div, calculate values of max, min scroll. While scrolling make sure value of translateX stays within bounds to avoid overscolling. Also show hide your arrow buttons too according to these values.
Here is a codesandbox with working demo.
Also adding the code below:
<template>
<div>
<nav class="navbar" ref="navbar">
<div class="left-arrow" v-if="translateX < 0">
<div class="left-arrow-button" v-on:click="moveLeft"><</div>
</div>
<div class="scroll-container" ref="scroll" :style="scrollStyle">
<div class="btn" v-for="(genre, index) in genres" :key="index">
{{ genre }}
</div>
</div>
<div class="right-arrow" v-if="translateX > minVal">
<div class="right-arrow-button" v-on:click="moveRight">></div>
</div>
</nav>
</div>
</template>
<script>
export default {
data() {
return {
genres: [
"GENRE 1",
"GENRE 2",
"GENRE 3",
"GENRE 4",
"GENRE 5",
"GENRE 6",
"GENRE 7",
"GENRE 8",
"REALITY TV",
"SPORTS",
"HORROR",
],
scrollWidth: 0,
navbarWidth: 0,
translateX: 0,
};
},
computed: {
scrollStyle: function () {
return {
transform: `translateX(${this.translateX}px)`,
};
},
minVal: function () {
return -(this.scrollWidth - this.navbarWidth);
},
},
mounted() {
this.getScrollWidth();
},
methods: {
getScrollWidth() {
this.scrollWidth = this.$refs.scroll.clientWidth;
this.navbarWidth = this.$refs.navbar.clientWidth;
// console.log(this.scrollWidth, this.navbarWidth);
},
moveRight() {
const newVal = this.translateX - this.navbarWidth / 2;
this.translateX = Math.max(this.minVal, newVal);
},
moveLeft() {
const newVal = this.translateX + this.navbarWidth / 2;
this.translateX = Math.min(0, newVal);
},
},
};
</script>
<style scoped>
.navbar {
display: flex;
flex-direction: row;
overflow: hidden;
position: relative;
/* width: 500px; */
}
.scroll-container {
display: flex;
}
.btn {
margin: 0 8px;
padding: 8px 16px;
background-color: grey;
border-radius: 16px;
}
.left-arrow {
position: absolute;
left: 0;
z-index: 1000;
height: 100%;
display: flex;
}
.left-arrow::after {
background: linear-gradient(to right, white 20%, rgba(255, 255, 255, 0) 80%);
height: 100%;
width: 50px;
content: "";
pointer-events: none;
}
.left-arrow-button {
display: flex;
align-items: center;
background-color: white;
cursor: pointer;
}
.right-arrow {
position: absolute;
right: 0;
z-index: 1000;
height: 100%;
display: flex;
}
.right-arrow::before {
background: linear-gradient(to left, white 20%, rgba(255, 255, 255, 0) 80%);
height: 100%;
width: 50px;
content: "";
pointer-events: none;
}
.right-arrow-button {
display: flex;
align-items: center;
background-color: white;
cursor: pointer;
}
</style>
I am using plain css and html tags, if you are using bootstrap or something like that for styling, you can easily update the code make use of that styling.
Also I have not implemented any animations to make the scrolling smooth, which I think youtube does. To keep the answer short and focused on a single problem, I have skipped that. You can try doing that yourself and if you face any problems, please ask a separate question for that.
I have seen similar questions on StackOverflow, but none of the answers have helped me so far.
I am using chart.js in my web project, but now, when I want to show 2 line graphs in the same container they do weird stuff, even if I adjust the parent container or try to wrap the canvas with a div.
here is some of the code:
[...]
<!--the big white container-->
<div class="Card Fill" id="5da78c0de214b0020e86f53a">
<div class="Details">
<span>entradas:30</span>
<span>duracion: 0:02</span>
</div>
<div class="ChartContainer">
<div class="ChartWraper">
<canvas id="tempGraph"></canvas>
</div>
<div class="ChartWraper">
<canvas id="ppmGraph"></canvas>
</div>
</div>
</div>
[...]
function drawChart(canvas, datasetLabel, data, labels) {
window[datasetLabel] = new Chart(
document.getElementById(canvas),
{
type: 'line',
data: {
datasets: [
{
lineTension: 0.1,
label: datasetLabel,
fill: true,
data: data
}
],
labels: labels
},
options: {
scales: {
xAxes: [{
display: true
}]
}
}
}
);
}
//ask api for data array
window.onload = function () {
let droneId = document.getElementsByClassName("Card")[0].id;
axios.get(`/api/vuelos/data/${droneId}`)
.then((response) => {
responseData = response.data;
let factorized = refractor(response.data); //separate the data from the drone into arrays
drawChart("tempGraph", "temperatura", factorized.temp, factorized.labels);
drawChart("ppmGraph", "particulas por millon", factorized.ppm, factorized.labels);
})
.catch((error) => {
alert(error.message);
console.log(error);
});
}
.Card {
position: relative; /* soo that i can relocate the options inside*/
background: var(--background);
padding: 30px;
border-radius: 15px;
margin: 10px;
box-shadow: var(--ui-shawdow);
}
.Fill{
width: 100%;
height: 100%;
}
.Card > .Details {
padding: 10px;
display: flex;
flex-direction: column;
align-content: space-around;
}
.ChartContainer{
display: flex;
flex-direction: column;
align-content: space-between;
height: 50%;
}
.ChartWraper{
height: 40%;
width: 100%;
}
canvas{
height: 40%;
}
What may I be doing wrong? I have tried changing things at the CSS or the Html but does not seem to work
I want it to look moething like this, responsive withouth overflow out of the card.
I want to build a state transition based on the "Animating State with Watchers" in the vue.js docs: https://v2.vuejs.org/v2/guide/transitioning-state.html#Animating-State-with-Watchers
I understand the example with primitive datatypes. In my case the data numbers, which I need to animate, are values in objects stored in an array.
Here is a simplified example of my problem:
new Vue({
el: '#animated-bar-demo',
data: {
bars: [
{
width: 30,
},
{
width: 10,
},
{
width: 70,
},
]
},
// HOW TO WATCH AND ANIMATE BARS.WIDTH FOR EACH BAR?
/* computed: {
animatedNumber: function() {
return this.tweenedNumber.toFixed(0);
}
},
watch: {
number: function(newValue) {
TweenLite.to(this.$data, 0.5, { tweenedNumber: newValue });
}
}, */
methods: {
changeData() {
let bars = this.bars;
bars.forEach(bar => {
bar.width += 10;
});
this.bars = bars;
},
},
})
body {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
button {
margin-bottom: 24px;
}
.bar-container {
width: 400px;
height: 32px;
border: 3px solid black;
overflow: hidden;
margin-bottom: 32px;
}
.bar {
width: 120%;
height: 100%;
background-color: #FA7268;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
<div id="animated-bar-demo">
<button v-on:click="changeData()"> add data </button>
<div class="bar-container" v-for="bar in bars">
<div class="bar"
:style="{ 'width': bar.width + '%'}">
</div>
</div>
</div>
I've commented the computed and watch logic for primitive values in my example out. How to watch and animate the width of each bar in the bars array?
I really appreciate your help and explanation for this problem.
Thanks!
I didn't found a solution to iterate an array through the tweenLite method but I got it to work.
I've created a component for the bar element and passing a single object width the width included.
Maybe someone with the same problem finds this solution helpful.
So basically I have a simple system with a list, where when I choose one item it displays a description and an image binded to that item.
The project: https://jsfiddle.net/jhnjcddh/2/
The problem is that I need to add the text inside my JS, thus If I would want to add any tags like <b></b>; or such I would Incase that text inside there:
map.set(item1, {
desc: '<a href=''>Hello!</a>',
...
});
BUT The outcome of this is the full form so like this:
<a href=''>Hello!</a>
I tried putting it into a var and appending:
var text1 = '<a href=''>Hello!</a>'
map.set(item1, {
desc: text1,
...
});
..but that gives the same outcome.
THE CODE:
// -----------------START OF STYLING ELEMENT-----------------
var btns = document.getElementsByClassName("item");
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener("click", function() {
var siblings = this.parentNode.childNodes
siblings.forEach(function(element) {
if (element.className){ // && element.className.indexOf('active') !== -1) { // TAKIT: Removed, see suggestion below
if (element.classList.contains('active')) // TAKIT: Suggestion: Easier, and better readability!
element.classList.remove("active");
}
})
this.classList.add("active"); // TAKIT: Suggestion instead of this.className += " active";
});
}
// -----------------END OF STYLING ELEMENT-----------------
// -----------------START OF LOGIC ELEMENT-----------------
const map = new Map();
// register item element as a key and object with corresponding description / image as value
map.set(item1, {
desc: 'text1',
img: 'https://pbs.twimg.com/profile_images/980681269859241984/-4cD6ouV_400x400.jpg'
});
map.set(item2, {
desc: 'some description item2',
img: 'https://78.media.tumblr.com/3d4a916d45190b2a58bec61f491cdb99/tumblr_p84af9767X1qhy6c9o1_500.gif'
});
map.set(item3, {
desc: 'some item3',
img: 'https://cdn.europosters.eu/image/1300/32201.jpg'
});
map.set(item4, {
desc: ' description for item4',
img: 'https://www.scribblefun.com/wp-content/uploads/2018/02/Pusheen-Coloring-Images.png'
});
map.set(item5, {
desc: 'This item5 is cool',
img: 'https://c1-zingpopculture.eb-cdn.com.au/merchandising/images/packshots/855db32a4fc24da2ba2ce821edd2a51e_Large.png'
});
map.set(item6, {
desc: 'item6 displays attitude',
img: 'https://c1-ebgames.eb-cdn.com.au/merchandising/images/packshots/969932eb9d274a57a59daf9e75319929_Medium.png'
});
map.set(item7, {
desc: 'amazing item7 just breathtaking',
img: 'https://images-na.ssl-images-amazon.com/images/I/81GErgo2%2B8L._SY355_.jpg'
});
map.set(item8, {
desc: ' item8 is an interesting conept',
img: 'https://cdn.shopify.com/s/files/1/2012/3849/products/4048862.jpg?v=1505815578'
});
// you can bind on click handler for example
const list = document.querySelectorAll('ol'); // TAKIT: Modified to return multiple elements
list.forEach(function() { // TAKIT: Added to manage the multiple elements
this.addEventListener('click', event => {
// if element that was registered in our map triggered the event
if (map.has(event.target)) {
var wrapper = event.target.closest('.wrapper'); // TAKIT: Get parent wrapper
// change text of description area
wrapper.querySelector('.description').textContent = map.get(event.target).desc; // TAKIT: Modified
// change src of the image
wrapper.querySelector('img').src = map.get(event.target).img; // TAKIT: Modified
}
});
});
// -----------------END OF LOGIC ELEMENT-----------------
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
/* containers */
#content-working {
margin: 20px;
}
* {
font-family: Corbel;
}
.wrapper {
border: 1px solid red;
padding: 10px;
display: inline-flex;
justify-content: center;
align-items: center;
}
.image,
.description,
.list {
border: 1px solid #472836;
box-sizing: border-box;
margin: 5px;
}
/* list */
.list {
width: 150px;
height: 250px;
background-color: #9AD2CB;
overflow-y: auto;
overflow-x: hidden;
}
.list ol {
list-style: none;
padding: 0;
margin: 0;
}
.list li {
padding: 5px;
transition: all .3s ease;
}
.list li:nth-child(even) {
background-color: #91f2e6;
width: 100%;
height: 100%;
}
.list li:hover,
.list .active {
cursor: pointer;
color: red;
padding-left: 25px;
}
/* sub-container */
.image,
.description {
width: 150px;
height: 150px;
}
.image {
background-color: #D7EBBA;
}
.image img {
width: 100%;
height: 100%;
}
.description {
background-color: #FEFFBE;
overflow-y: auto;
overflow-x: hidden;
height: 95px;
}
<div id="content-working">
<div class="wrapper">
<div class="list">
<ol>
<li id="item1" class="item">items1</li>
<li id="item2" class="item">items2</li>
<li id="item3" class="item">items3</li>
<li id="item4" class="item">items4</li>
</ol>
</div>
<div class="image-container">
<div class="image">
<img src="https://semantic-ui.com/images/wireframe/image.png" alt="">
</div>
<div class="description">
just a placeholder text for when nothing has been chosen.
</div>
</div>
</div>
<div class="wrapper">
<div class="list">
<ol>
<li id="item5" class="item">items5</li>
<li id="item6" class="item">items6</li>
<li id="item7" class="item">items7</li>
<li id="item8" class="item">items8</li>
</ol>
</div>
<div class="image-container">
<div class="image">
<img src="https://semantic-ui.com/images/wireframe/image.png" alt="">
</div>
<div class="description">
just a placeholder text for when nothing has been chosen.
</div>
</div>
</div>
</div>
What am I doing wrong here? Why isn't this working?
You're setting everything through textContent.
Use innerHTML to allow tags being interpreted.
https://jsfiddle.net/pd6o0zfn/ (items7)
On line 70 of your Jsfiddle :
wrapper.querySelector('.description').textContent = map.get(event.target).desc; // TAKIT: Modified
You need to replace .textContent by .innerHTML as follow to keep your HTML formatting :
wrapper.querySelector('.description').innerHTML = map.get(event.target).desc; // TAKIT: Modified
i have a vertical list of items, each of which can be removed. I put my items inside a transition-group and created simple opacity and transform transitions for them. The transitions on the removed elements work as expected, however if I remove an element which is not placed at the bottom, the ones beneath just jump up and take its place without any transition. I Can't find a way to target this behaviour.
All I want is just that the elements below slide up smoothly.
Is there any way to achieve this effect by using css transitipms and Vue's animation hooks?
Here is a demo: https://jsfiddle.net/gcp18nq0/
Template:
<div id="app">
<div class="form">
<label for="name">Name</label>
<input type="text" id="name" v-model="name">
<button #click="addPlayer">Add player</button>
</div>
<div class="players">
<transition-group name="player">
<div class="panel" v-for="player in players" :key="player.id">
<h2>
{{ player.name}}
<span class="remove" #click="removePlayer(player.id)">Remove</span>
</h2>
</div>
</transition-group>
</div>
</div>
Script:
data() {
return {
name: "",
players: [
{id: 1, name: 'Player1'},
{id: 2, name: 'Player2'},
{id: 3, name: 'Player3'},
]
}
},
methods: {
addPlayer: function () {
//,,,,
},
removePlayer: function (playerId) {
//...
}
}
});
CSS
.form {
margin:0 auto;
width:400px;
}
.panel {
width: 400px;
margin: 10px auto;
overflow: hidden;
border: 1px solid;
text-align: center;
}
.remove {
float: right;
cursor: pointer;
text-decoration: underline;
font-size: 12px;
vertical-align: bottom
}
.player-enter,
.player-leave-to
/* .fade-leave-active below version 2.1.8 */
{
opacity: 0;
}
.player-enter {
transform: translateY(30%);
}
.player-leave-to {
transform: translateX(30%);
}
.player-enter-active,
.player-leave-active {
transition: all 1.5s;
}
.player-move {
transition: all 1.5s;
}
The only working way I found was by adding position:absolute on "player-leave-active" state but since the element collapses it changes its vertical position, which is not the desired effect. I also tried changing the height but there the elements below still jump up a bit after the height is set to 0.
Im sure that this can be achieved easily with jQuery but i believe that there should be a way to do it without js.
Thank you in advance!
p.s. its my first post here, so i hope that it was explained clearly enough.
So I made some small tweaks to your fiddle: https://jsfiddle.net/gcp18nq0/1/ and hopefully that is what you looking for.
The most important change was setting display: inline-block on the .panel class, according to the Vue documentation:
One important note is that these FLIP transitions do not work with
elements set to display: inline. As an alternative, you can use
display: inline-block or place elements in a flex context.
new Vue({
el: "#app",
data() {
return {
name: "",
players: [{
id: 1,
name: 'Batman'
},
{
id: 2,
name: 'Robin'
},
{
id: 3,
name: 'Superman'
},
{
id: 4,
name: 'Spiderman'
},
]
}
},
methods: {
addPlayer: function() {
const newPlayer = {
id: this.players.length + 1,
name: this.name,
};
this.players.push(newPlayer);
},
deletePlayer: function(playerId) {
let playerToRemove = this.players.find((player) => {
return player.id === playerId;
});
let playerIndex = this.players.indexOf(playerToRemove);
this.players.splice(playerIndex, 1);
}
}
});
.form {
margin: 0 auto;
width: 400px;
}
.panel {
width: 400px;
margin: 6px auto;
overflow: hidden;
border: 1px solid;
text-align: center;
transition: all 1s;
display: inline-block;
}
.players {
position: relative;
text-align: center;
}
.remove {
float: right;
cursor: pointer;
text-decoration: underline;
font-size: 12px;
vertical-align: bottom
}
.player-enter,
.player-leave-to {
opacity: 0;
}
.player-enter {
transform: translateY(30%);
}
.player-leave-to {
transform: translateX(300%);
}
.player-leave-active {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<div class="form">
<label for="name">Name</label>
<input type="text" id="name" v-model="name">
<button #click="addPlayer">Add player</button>
</div>
<div class="players">
<transition-group name="player" tag="div">
<div class="panel" v-for="player in players" :key="player.id">
<h2>
{{ player.name}}
<span class="remove" #click="deletePlayer(player.id)">Remove</span>
</h2>
</div>
</transition-group>
</div>
</div>