Render JSX-elements as multi-level pyramid - javascript

I need to create a pyramid figure with 4 levels division something like this:
Although I have achieved the same with the following code set:
const PyramidChart = () => {
return (
<div className="d-flex flex-column align-items-center pyramid_wrap">
<div className="category_one">
<h6>2</h6>
</div>
<div className="category_two">
<h6>8</h6>
</div>
<div className="category_three">
<h6>11</h6>
</div>
<div className="category_four">
<h6>16</h6>
</div>
</div>
);
};
ReactDOM.render(
<PyramidChart />,
document.getElementById('root')
);
.pyramid_wrap {
height: 100%;
text-align: center;
}
.category_one {
width: 70px;
height: 30px;
border-left: 35px solid transparent;
border-right: 35px solid transparent;
border-bottom: 50px solid tomato;
}
.category_two {
width: 116px;
height: 30px;
border-left: 22px solid transparent;
border-right: 22px solid transparent;
border-bottom: 28px solid orange;
}
.category_three {
width: 162px;
height: 30px;
border-left: 22px solid transparent;
border-right: 22px solid transparent;
border-bottom: 28px solid cyan;
}
.category_four {
width: 208px;
height: 30px;
border-left: 22px solid transparent;
border-right: 22px solid transparent;
border-bottom: 28px solid teal;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
And got something like this:
But the issue I am unable to resolve is to align the text in top-level pyramid area to center. It is somehow always out of sync with the it is aligned in.
Any help to rectify this issue is appreciated :)

Let's try moving from CSS Triangle to :before and :after with skew transform property.
I haven't changed your HTML, just add a .value class to your <h6> to style them.
Basically all you have to do is to set to every <div> a :before with a negative degree angle and a :after with a positive degree angle and give them the same background color, in order to match.
Only for the top-level <div> I used a CSS Triangle, so you already know how it works.
In order to make the values horizontally and vertically aligned I used flexbox on the parent, combined with justify-content: center; (horizontal align) and align-items: center; (vertical align). Don't forget to always add a line-height:1em; and remove margin to the element you want to be vertical aligned. If the line-height is not equal to its actual height it will always be some pixels up or down the middle axe.
.pyramid_wrap {
margin-top: 200px;
height: 100%;
text-align: center;
}
.category_one,
.category_two,
.category_three,
.category_four {
position: relative;
margin: 6px auto;
display: flex;
justify-content: center;
align-items: center;
}
.category_one:before,
.category_one:after,
.category_two:before,
.category_two:after,
.category_three:before,
.category_three:after,
.category_four:before,
.category_four:after {
position: absolute;
top: 0;
display: block;
width: 30px;
height: 100%;
content: "";
}
.category_one:before,
.category_two:before,
.category_three:before,
.category_four:before {
left: -15px;
transform: skew(-25deg);
}
.category_one:after,
.category_two:after,
.category_three:after,
.category_four:after {
right: -15px;
transform: skew(25deg);
}
.category_one {
width: 20px;
height: 40px;
background: tomato;
}
.category_one:before,
.category_one:after {
width: 30px;
height: 40px;
background: tomato;
}
.category_one:before {
left: -16px;
top: 0;
}
.category_one:after {
right: -16px;
top: 0;
}
.category_one .value:after {
position: absolute;
z-index: -1;
top: -55px;
left: 50%;
transform: translateX(-50%);
content: "";
display: block;
width: 0;
height: 0;
border-style: solid;
border-width: 0 28px 70px 28px;
border-color: transparent transparent tomato transparent;
}
.category_two {
width: 70px;
height: 40px;
background: orange;
}
.category_two:before,
.category_two:after {
width: 40px;
background: orange;
}
.category_two:before {
left: -12px;
}
.category_two:after {
right: -12px;
}
.category_three {
width: 120px;
height: 40px;
background: cyan;
}
.category_three:before,
.category_three:after {
background: cyan;
}
.category_three:before {
left: -9px;
}
.category_three:after {
right: -9px;
}
.category_four {
width: 150px;
height: 40px;
background: teal;
}
.category_four:before,
.category_four:after {
background: teal;
}
.value {
margin: 0;
position: relative;
z-index: 3;
color: #fff;
font-size: 16px;
font-family: Arial, sans-serif;
font-weight: normal;
line-height: 1em;
}
<div class="d-flex flex-column align-items-center pyramid_wrap">
<div class="category_one">
<h6 class="value">2</h6>
</div>
<div class="category_two">
<h6 class="value">8</h6>
</div>
<div class="category_three">
<h6 class="value">11</h6>
</div>
<div class="category_four">
<h6 class="value">16</h6>
</div>
</div>

One of the possible solutions is to use CSS mask over the entire pyramid's wrapper. It may be not well supported as of now, yet provides genuine way of shaping your pyramid with SVG path (triangle):
In order not to let the mask shrink (hiding upper and lower layers), you may need to preserve the min-width of a wrapper (equal to number of layers multiplied by layer height - 3em).
To avoid layer labels falling outside of the mask, you may need to center the mask according to labels position (mask-position: center)
Live-demo, implementing that concept, might look as follows:
const { render } = ReactDOM,
rootNode = document.getElementById('root')
const pyramidItems = [{label:'2', color: '#f47660'}, {label:'8', color:'#fcae60'}, {label:'11', color:'#a7e6db'}, {label:'16',color:'#79d4c5'}]
const Pyramid = ({items}) => (
<div
className="wrapper"
style={{minWidth:`${items.length}*3em`}}
>
{
items.map(({label,color},key) => (
<div
key={key}
className="layer"
style={{backgroundColor:color}}
>
{label}
</div>
))
}
</div>
)
render (
<Pyramid items={pyramidItems} />,
rootNode
)
.wrapper {
--triangle-shape: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNTAsMCBMMTAwLDEwMCBMMCwxMDAgeiIvPjwvc3ZnPg==");
-webkit-mask-image: var(--triangle-shape);
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-image: var(--triangle-shape);
mask-repeat: no-repeat;
mask-position: center;
display: flex;
flex-direction: column;
}
.layer {
height: 3em;
color: #fff;
font-size: 15px;
display: flex;
justify-content: center;
align-items: center;
border: .2em solid #fff;
margin: -.2em 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

Related

Why does my page width only stretch to 100% with "fit-content"?

I have tried looking around for this but can't seem to find a question to match my current problem. I am trying to build a mock ecommerce website to practice using React. I have a header component. I want this component to be 100% of the screen width, so that the elements inside this component shrink whenever the page shrinks. I have some global css that sets the height and width of the html and body to 100%:
html, body{
background-color: rgb(167, 72, 72);
height: 100%;
min-width: 100%;
}
I am currently facing two problems, neither of which I understand very well the causes of. When I set my header component (the outermost component) to have a width of 100%, the page shrinks correctly. But when I open up developer tools to check the responsiveness, something goes wrong so that the right side of my header is shrinking faster than the page header_shrink
I am able to fix this by setting the width of my header to "fit-content" instead of "100%". Here is what the header looks like when I shrink the page using developer tools.header_fixed But when I do it this way, the components inside of my header don't shrink correctly. For example, my search bar is supposed to decrease in width as I shrink the page, but when I use "fit-content", it just stays set to whatever size it is. search-bar-constant. When I have the width set to 100% instead of fit content, it looks the way it's supposed to search-bar-fixed.
Sorry for the long explanation, but this is the bulk of my problem. "Width: 100%" allows the items in my header component to shrink correctly, but not the component itself. And "width: fit-content" allows the outer header component to shrink correctly, but not the items inside of it.
Here is the JSX I have for reference:
import React from 'react'
import './Header.css'
import { BiSearchAlt2 as SearchIcon} from "react-icons/bi";
import {RiArrowDropDownLine as DropDownIcon} from "react-icons/ri";
import { CgProfile as Profile } from "react-icons/cg";
import { CgShoppingCart as Cart } from "react-icons/cg";
const Header = () => {
const texts = [
'ORDERS OF $5K SHIP FREE',
'FREE SHIPPING ON SELECT ITEMS: SHOP NOW',
'BUY A RIG AND YOUR ENTIRE ORDER SHIPS FREE'
];
let currentTextIndex = 0;
setInterval(() => {
const shippingDealsText = document.querySelector('.shipping-deals-text');
shippingDealsText.classList.add('out');
setTimeout(() => {
shippingDealsText.textContent = texts[currentTextIndex];
shippingDealsText.classList.remove('out');
currentTextIndex = (currentTextIndex + 1) % texts.length;
}, 1000);
}, 5000);
return (
<div className="header">
<div className="header-top">
<div className="top-logo">
<h5 className='small-logo'>LEVIATHAN</h5>
</div>
<div className="space"></div>
<div className="link-container">
<div className="link-wrap">
Gift Cards
</div>
<div className="link-wrap">
Contact Us
</div>
<div className="link-wrap">
Order Status
</div>
<div className="link-wrap">
Live Chat
</div>
</div>
</div>
<div className="header-middle">
<div className="middle-logo">
<h5 className='big-logo'>LEVIATHAN</h5>
</div>
<div className="search-container">
<div className="search-wrapper">
<input
type="text"
id="search-bar"
placeholder="Search"
className='search'
/>
<div className="search-icon-wrapper">
<SearchIcon className='search-icon'/>
</div>
</div>
</div>
<div className="shipping-deals-container">
<div className="button-container">
<div className="shipping-deals-button">
<span className="deals-text">DAILY SHIPPING DEALS </span>
</div>
</div>
<div className="text-container">
<div className="text-slideshow">
<p className="shipping-deals-text">BUY A RIG AND YOUR ENTIRE ORDER SHIPS FREE</p>
</div>
</div>
</div>
<div className="icons-right">
<Profile className='login-pic'/>
<span>Log In</span>
<Cart className='shopping-cart'/>
</div>
</div>
<div className="header-bottom">
<div className="nav-bar">
<ul className='navigation'>
<li className='menu-items'>
<a href="/" className='button drop-down red'>Shop <DropDownIcon className='drop-icon'/></a>
<a href="/" className='button'>Equipment for Crossfit</a>
<a href="/" className='button'>New Gear</a>
<a href="/" className='button'>Barbells</a>
<a href="/" className='button'>Plates</a>
<a href="/" className='button'>Rigs and Racks</a>
<a href="/" className='button'>Shoes</a>
<a href="/" className='button'>Apparel</a>
<a href="/" className='button'>3 Ships Free</a>
<a href="/" className='button'>Zeus</a>
<a href="/" className='button drop-down'>The Index</a>
</li>
</ul>
</div>
</div>
</div>
)
}
export default Header
Here is the styling I am currently applying:
.header {
min-width: 100%;
margin: 0;
padding: 0;
}
.header-top {
background-color: white;
display: flex;
height: 2.5rem;
width: 100%;
}
.top-logo {
position: relative;
margin-left: 3rem;
}
.space {
flex-grow: 1;
}
.small-logo {
padding-top: 0.5em;
position: relative;
font-size: larger;
color: rgb(133, 133, 133)
}
.link-container {
display: flex;
/*border: 1px solid red;*/
margin-right: 3rem;
}
.link-wrap {
/*border: 1px solid green;*/
font-size: 14px;
padding-left: 1rem;
padding-top: 0.75rem;
}
.link-wrap a {
text-decoration: none;
color:#666666;
cursor: pointer;
}
/* Large section of header, black background */
.header-middle {
background-color: black;
height: 7rem;
display: flex;
}
/* Big LEVIATHAN text */
.middle-logo {
/*border: 1px solid red;*/
position: relative;
margin-left: 3rem;
display: flex;
justify-content: center;
align-items: center;
cursor: co;
}
.big-logo {
font-size: 48px;
/*padding-top: 2rem;*/
position: relative;
color: white;
}
.big-logo:hover {
color: rgb(210, 0, 0);
}
.search-container {
position: relative;
width: 40%;
display: flex;
flex-basis: 60%;
margin-left: 3rem;
align-items: center;
justify-content: center;
}
/*This is what has the appearance of the search bar*/
.search-wrapper {
min-width:100%;
height: 35%;
position: relative;
background-color: white;
display: flex;
flex-basis: 50%;
}
.search-icon-wrapper {
display: flex;
justify-content: center;
align-items: center;
width: 3rem;
}
.search-icon {
color: black;
font-size: 20px;
}
/*This is the actual search bar tucked inside*/
.search {
width: 100%;
height: 100%;
border: none;
outline: none;
margin-left: 1em;
font-size: 17px;
}
.search::placeholder {
color:rgb(94, 94, 94);
}
/* This holds onto both our daily shipping deals button */
/* and our text slideshow */
.shipping-deals-container{
width: 18em;
margin-left: 2.5em;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
/*border: 2px solid rgb(136, 77, 255);*/
}
.shipping-deals-button {
width: 65%;
height: 44%;
background-color: rgb(234, 2, 2);
position: relative;
display: flex;
justify-content: center;
align-items: center;
margin-top: 1.5em;
}
.button-container {
width: 100%;
height: 50%;
/*border: 2px solid magenta;*/
}
.deals-text {
color: white;
font-size: 12px;
position: relative;
text-align: center;
align-items: stretch;
width: 100%;
}
.text-container {
/*border: 2px solid rgb(20, 182, 11);*/
width: 100%;
height: 50%;
}
.text-slideshow {
color: white;
width: 100%;
height: 50%;
font-size: 12px;
}
.shipping-deals-text {
transition: opacity 1s;
opacity: 1;
font-size: 13px;
}
.out {
opacity: 0;
transition: opacity 1s;
}
.shipping-deals-text-red{
color: red;
}
.navigation {
display: flex;
align-items:flex-start;
height: 3rem;
}
.menu-items {
height: 100%;
margin-left: 1.5rem;
padding-right: 1.5rem;
display: flex;
align-items: flex-start;
flex: 1;
}
ul {
list-style-type: none;
background-color: #333333;
}
.button {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
text-decoration: none;
color: white;
text-transform: uppercase;
white-space: nowrap;
padding: 1em;
font-weight: bold;
}
.button:hover {
color:rgb(210, 0, 0)
}
.red {
background-color: rgb(210, 0, 0);
}
.red:hover {
color: white;
}
.drop-icon {
font-size: 25px;
}
.icons-right {
height: 50%;
margin-top: 2em;
min-width: 10%;
display: flex;
justify-content: center;
align-items: center;
margin-right: 1rem;
}
.login-pic {
color: white;
font-size: 20px;
}
.shopping-cart {
color: white;
font-size: 20px;
margin-left: 1rem;
}
.icons-right span {
color: white;
margin-left: 0.5em;
}
#media (max-width: 1025px) {
.shipping-deals-container {
display: none;
}
.header-top {
display: none;
}
.header-middle {
height: 50%;
}
.search-wrapper {
border: 2px solid white;
height: 2rem;
}
.icons-right {
margin-bottom: 2rem;
}
}
I have tried altering the width of my body, and html, but nothing seems to be giving me the solution I am looking for
With width: 100% on .header it shrinks the header the way you want it. That seems to be correct actually.
The element that prevents shrinking is <li class="menu-items"></li> because of display: flex;. Flexbox is by default not wrapping (flex-wrap: nowrap;).
Add flex-wrap. wrap; and you'll see everything will shrink with fit-content or width: 100%;
Hope this helps.
On another note: You shouldn't use <li> (List-Element) as the list. Thats what <ul> (Unsorted list) is for.
It should look more like this ->
<ul>
<li>
Shop
</li>
<li>
Equipment for Crossfit
</li>
<li>
New Gear
</li>
<!-- ... -->
</ul>

Relative position disables sticky position of sibling element CSS

I make a game for a project that you must find the real neighbours of a country.
I have this HTML code:
.game-panel {
gap: 10px;
display: grid;
grid-template-columns: 200px auto;
grid-template-rows: 5.5em 22px auto;
gap: 10px;
grid-template-areas: "side main" "side main2" "side main3";
}
#sidebar {
width: 200px;
border-radius: 5px;
background-color: #ff1493;
display: flex;
grid-area: side;
flex-direction: column;
z-index: 100;
}
#playing-country {
display: flex;
border-radius: 5px;
background-color: #708090;
grid-area: main;
}
#progress {
-webkit-appearance: none;
display: flex;
position: sticky;
top: 0;
height: 24px;
grid-area: main2;
height: 24px;
border: 1px solid #ccc;
color: #aaa;
width: 100%;
border-radius: 5px;
z-index: 102;
}
#progress[value]::-webkit-progress-bar {
background: #f1f1f1;
}
#progress[value]::-webkit-progress-value {
background: blue;
}
#neighbours-panel {
border-radius: 5px;
grid-area: main3;
display: flex;
flex-shrink: 1;
background-color: #fff;
}
.overlay {
position: absolute;
padding: 0 !important;
margin: 0 !important;
background-color: rgba(136, 132, 132, 0.5);
}
<div class="game-panel">
<div id='sidebar'>
<h3>Βρες τους γείτονες</h3>
<div id="round">Γύρος: <span id='round-text'>0</span></div>
<div id="score">Σκορ: <span id='score-text'>0</span></div>
<button id="btn-next-round"><span>Επόμενη χώρα</span></button>
<button id="btn-new-game"><span>Νέο παιχνίδι</span></button>
</div>
<div id="playing-country">playing-country</div>
<progress id='progress' max='100' value='0'></progress>
<div id='neighbours-panel'>
</div>
</div>
In the #neighbours-panel I dynamically create divs with country choices and when I click the right ones, a semi-transparent div with overlay class is created inside the #neighbours-panel that covers only the #neighbours-panel with the countries. To do this I add position:relative; to the parent #neighbours-panel and position:absolute; to the newly created overlay div. The #progress must be sticky on the top of the page when scroll down. The problem is that with position:relative; on the #neighbours-panel in order to work properly the overlay, the position:sticky; of the #progress is disabled. If I remove position:relative; then the sticky position works as it should but then the overlay div covers the whole page and not only the #neighbours-panel as it should. Can anyone help me find out what to do?

How to fit elements at slider container

I work with statick creation elements, and for this i need to have slider.
I just create slider, but elements are not fit in to slider container.
Example:
var htmlElements;
var userName = "Jonny Programmer"
var id = "6656"
function createUserCard() {
htmlElements = `<div class="user-card">
<img src="https://source.unsplash.com/user/erondu" class="userImage" />
<div class="info">
<div class="name">${userName}</div>
<div class="handle">
<div class="idPersone">${id}</div>
</div>
</div>
</div>`
$('.cardsCreation').append(htmlElements);
}
$('#plus-button').on('click', function () {
createUserCard();
});
(function () {
var sliderImages = document.querySelectorAll('.image'),
arrowLeft = document.querySelector('#left-arrow'),
arrowRight = document.querySelector('#right-arrow'),
currentImg = 0;
function initSlider() {
resetSlider();
sliderImages[0].style.display = 'block';
}
function resetSlider() {
for (var i = 0; i < sliderImages.length; i++) {
sliderImages[i].style.display = 'none';
}
}
function toLeft() {
resetSlider();
sliderImages[currentImg - 1].style.display = 'block';
currentImg--;
}
function toRight() {
resetSlider();
sliderImages[currentImg + 1].style.display = 'block';
currentImg++;
}
arrowLeft.addEventListener('click', function () {
if (currentImg === 0) {
currentImg = sliderImages.length;
}
toLeft();
});
arrowRight.addEventListener('click', function () {
if (currentImg === sliderImages.length - 1) {
currentImg = -1;
}
toRight();
});
initSlider();
})();
.user-card, userImage {
box-shadow: 0px 2px 5px 0 rgba(0,0,0,0.25);
}
.user-card {
margin: 12px;
padding: 10px 10px 10px 10px;
border-radius: 12px;
width: 160px;
text-align: center;
float: left;
background-color: #fff;
}
.userImage {
border-radius: 50%;
height: 140px;
width: 140px;
border: 5px solid #eee;
}
.name {
font-size: 20px;
margin: 5px;
font-weight: bold;
}
.progress {
color: #25af53;
font-size: 48px;
}
#plus-button {
width: 55px;
height: 55px;
border-radius: 50%;
background: #428bca;
position: absolute;
top: 33%;
margin-left: 20px;
cursor: pointer;
box-shadow: 0px 2px 5px #666;
border: none;
outline: none;
}
.plus {
color: white;
position: absolute;
top: 0;
display: block;
bottom: 0;
left: 0;
right: 0;
text-align: center;
padding: 0;
margin: 0;
line-height: 55px;
font-size: 38px;
font-family: 'Roboto';
font-weight: 300;
}
#plus-button:hover {
box-shadow: 0 6px 14px 0 #666;
transform: scale(1.05);
}
.wrapper {
position: relative;
}
.arrow {
cursor: pointer;
position: absolute;
width: 0;
height: 0;
border-style: solid;
top: 50%;
margin-top: 160px;
}
#left-arrow {
border-width: 50px 40px 50px 0;
border-color: transparent #000 transparent transparent;
left: 0;
margin-left: 25px;
}
#right-arrow {
border-width: 50px 0 50px 40px;
border-color: transparent transparent transparent #000;
right: 0;
margin-right: 25px;
}
.image {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.vertical-align-wrapper {
display: table;
overflow: hidden;
text-align: center;
}
.vertical-align-wrapper span {
display: table-cell;
vertical-align: middle;
font-size: 5rem;
color: #ffffff;
}
#media only screen and (max-width : 992px) {
.vertical-align-wrapper span {
font-size: 2.5rem;
}
#left-arrow {
border-width: 30px 20px 30px 0;
margin-left: 15px;
}
#right-arrow {
border-width: 30px 0 30px 20px;
margin-right: 15px;
}
.arrow {
margin-top: -30px;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<div id="left-arrow" class="arrow"></div>
<div id="slider">
<div class="image image-one">
<div class="vertical-align-wrapper">
<div class="cardsCreation"></div>
<button id="plus-button">
<p class="plus">+</p>
</button>
</div>
</div>
<div class="image image-two">
<div class="vertical-align-wrapper">
<span>Slide 2</span>
</div>
</div>
<div class="image image-three">
<div class="vertical-align-wrapper">
<span>Slide 3</span>
</div>
</div>
</div>
<div id="right-arrow" class="arrow"></div>
</div>
So as u can see affter tapping "+" i add new ellement in to html.
And from two sides i have arrows which are changing slider.
After tapping arrows go down, and this is not good.
And after i will reach limit of adding element in one slider it's add new element to new slider page.
What i want ex:
If you are using toggle of display CSS property that happened because it remove whole element from the DOM so, I suggest you to use visibility or opacity properties to done your task.

JS - Click event not working as it should

Maybe some of you can help me solve this problem.
I have this code and I made like an extension for the product that will show product description when click on product. But the on click function isn't working (can't close description).
Thanks!
$('.product').on('click', function(){
$('.product .productExtension').css("display","none");
$(this).children(".productExtension").css("display","block");
});
function close(){
$('.productExtension').css("display","none");
}
.product{
position: relative;
width: 80px; height: 160px;
padding: 20px;
border: solid 1px grey;
text-align: center; font-family: Arial;
}
.product > .productExtension{
position: fixed;
top: 50%; left: 50%; transform: translate(-50%,-50%);
width: 300px; height: 200px;
padding: 20px;
background: red;
text-align: left;
display: none;
}
.product > .productExtension > .closeProductExtension{
position: absolute;
top: -40px; left: 0;
width: 20px; height: 20px;
margin: 10px;
border: none;
background: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="product">
<div class="productName">Red Hoodie</div>
<div class="productPrice">14.72$</div>
<div class="productExtension">
<div class="productDescription">This hoodie is in red color</div>
<div class="closeProductExtension" onclick="close()">Close</div>
</div>
</div>
Now I know it wasn't fully part of the question, and I took a bit of liberty with the styling, but there is absolutely no need to hide all different productExtension classes upon each close. It would be far lighter to read the properties detailed inside your product div, and render them to a modal.
It does have an overly complex way of closing the modal ( just some liberties at work there, I am sorry for that one :) )
The answer that is currently accepted both details the reason why you cannot use close (could be window.close), I just thought the offer a different perspective when you have more than one product how to transfer the data between a modal and your DOM as you describe it now. I think it has its advantages
window.addEventListener( 'load', function() {
document.querySelectorAll('.product').forEach( product => {
product.addEventListener('click', handleProductClicked, false );
} );
document.querySelectorAll('[data-action]').forEach( action => {
action.addEventListener('click', handleAction );
} );
function handleAction( e ) {
const owner = e.currentTarget;
const action = owner.getAttribute('data-action');
const selector = owner.getAttribute('data-target');
const target = document.querySelector( selector );
if (action === 'hide') {
if ( !target.classList.contains('hidden') ) {
target.classList.add('hidden');
}
}
}
function showModal( title, content, owner ) {
const modal = document.querySelector('#modal');
if ( modal.classList.contains('hidden') ) {
modal.classList.remove( 'hidden' );
}
modal.querySelector('[data-for=title]').innerText = title;
modal.querySelector('[data-for=content]').innerHTML = content;
}
function handleProductClicked( e ) {
const productContainer = e.currentTarget;
const name = productContainer.querySelector('.productName').innerText;
const description = productContainer.querySelector('.productExtension').innerHTML;
showModal( name, description, productContainer );
}
} );
.hidden {
display: none;
visibility: hidden;
}
.productExtension {
display: none;
visibility: hidden;
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 300px;
height: 200px;
border: solid #a0a0a0 1px;
box-shadow: 5px 3px 5px #777;
background-color: #cfcfcf;
}
.modal > .title {
position: absolute;
left: 0px;
top: 0px;
right: 0px;
height: 20px;
font-size: 0.9em;
background-color: blue;
border-bottom: solid #fff 1px;
}
.modal > .title > .controls {
position: absolute;
right: 0px;
top: 0px;
width: 20px;
height: 18px;
background-color: #efefef;
border: solid #a0a0a0 1px;
text-align: center;
text-transform: small-caps;
}
.controls:hover {
font-weight: bold;
cursor: pointer;
}
.modal > .title > [data-for] {
padding: 3px;
color: #fff;
font-weight: 800;
}
.modal > .content {
position: absolute;
left: 0px;
right: 0px;
top: 21px;
bottom: 0px;
padding: 5px;
border: inset #666 1px;
}
.product {
position: relative;
width: 80px;
height: 160px;
padding: 20px;
border: solid 1px grey;
text-align: center;
font-family: Arial;
}
<div class="modal hidden" id="modal">
<div class="title">
<span data-for="title"></span>
<div class="controls">
<span data-action="hide" data-target="#modal">X</span>
</div>
</div>
<div class="content" data-for="content">
</div>
</div>
<div class="product">
<div class="productName">Red Hoodie</div>
<div class="productPrice">14.72$</div>
<div class="productExtension">
<div class="productDescription">This hoodie is in red color</div>
</div>
</div>
<div class="product">
<div class="productName">Blue Hoodie</div>
<div class="productPrice">14.75$</div>
<div class="productExtension">
<div class="productDescription">This hoodie is in blue color</div>
</div>
</div>
This is happening because both functions trigger. The first trigger because you are clicking on an item that is inside the DIV “product” and the second because you’ve passed the function to the onClick. You should take out the “productExtension” div from “product” to make it works.
As mentioned in other comments, you have two click handler in the parent and child. The parent div is intercepting all click events. Try this for your requirement.
$(".product").on("click", function(e) {
$(".product .productExtension").css("display", "none");
$(this)
.children(".productExtension")
.css("display", "block");
if (e.target.classList.contains('closeProductExtension')) {
$(".productExtension").css("display", "none");
}
});
You have several problems. The first is that you trigger the open event as well. To solve this you can use stop propagation. The second is that you are using the method name "close" which is already used in JS.
$('.product').on('click', function() {
$('.product .productExtension').css("display", "none");
$(this).children(".productExtension").css("display", "block");
});
function closeE(event) {
event.stopPropagation();
$('.productExtension').css("display", "none");
}
.product {
position: relative;
width: 80px;
height: 160px;
padding: 20px;
border: solid 1px grey;
text-align: center;
font-family: Arial;
}
.product>.productExtension {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 300px;
height: 200px;
padding: 20px;
background: red;
text-align: left;
display: none;
}
.product>.productExtension>.closeProductExtension {
position: absolute;
top: -40px;
left: 0;
width: 20px;
height: 20px;
margin: 10px;
border: 1px solid black;
background: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="product">
<div class="productName">Red Hoodie</div>
<div class="productPrice">14.72$</div>
<div class="productExtension">
<div class="productDescription">This hoodie is in red color</div>
<div class="closeProductExtension" onclick="closeE(event)">Close</div>
</div>
</div>

How do I make a button like the JoinVideoCallButton in Discord?

JoinVideoCallButton
Hi!
I simply wonder if anyone here have any idea how to make such a hover effect that could be used for such double option buttons or navbars etc.
I have the css and html code for it but I have no idea how to make the effect work. Also, I wonder if it is possible without the use of Jquery?
body {
background-size: 100%;
margin: 0px;
background-color: gray;
}
.joinVideoCallButton-2Pohj0 {
-ms-flex-align: center;
-ms-flex-pack: center;
-webkit-box-align: center;
-webkit-box-pack: center;
-webkit-box-sizing: border-box;
align-items: center;
background-color: #43b581;
border-radius: 3px;
box-sizing: border-box;
cursor: pointer;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
height: 36px;
justify-content: center;
overflow: hidden;
padding: 0 14px;
position: relative;
}
.joinVideoCallButton-2Pohj0 .underlay-1LCk7B {
-webkit-transition: opacity .1s ease;
background-color: #69c49a;
border-radius: 3px;
bottom: 0;
left: 0;
margin: 2px;
position: absolute;
top: 0;
transition: opacity .1s ease;
width: 50%;
flex: 1 1 auto;
}
.joinVideoCallButton-2Pohj0 .inner-1e8n63 {
cursor: pointer;
z-index: 1;
flex: 1 1 auto;
}
.flex-lFgbSz:first-child, .horizontal-2BEEBe>.flexChild-1KGW5q:first-child {
margin-left: 0;
}
.flex-lFgbSz, .horizontal-2BEEBe>.flexChild-1KGW5q {
margin-left: 10px;
margin-right: 10px;
}
.joinVideoCallButton-2Pohj0 .callDivider-_hMb9n {
background-color: #69c49a;
height: 20px;
margin: 0 2px;
opacity: .6;
width: 2px;
}
.joinVideoCallButton-2Pohj0 .icon-3tOP24 {
height: 16px;
width: 16px;
}
.joinVideoCallButton-2Pohj0 .buttonText-1b8lyZ {
color: #f6f6f7;
font-size: 14px;
margin-left: 8px;
margin-right: 8px;
text-transform: uppercase;
}
.wrapper-1XTKlU{
background-size:cover;
min-height:100vh;
overflow:hidden
}
.flexWrapper-1ztpWj{
height:100vh
}
.flex-3B1Tl4{
display:-webkit-box;
display:-ms-flexbox;
display:flex
}
.alignStart-pnSyE6{
-ms-flex-align:start;
-webkit-box-align:start;
align-items:flex-start
}
.alignEnd-3PVyen{
-ms-flex-align:end;
-webkit-box-align:end;
align-items:flex-end
}
.alignCenter-3VxkQP{
-ms-flex-align:center;
-webkit-box-align:center;
align-items:center
}
.alignStretch-1hwxMa{
-ms-flex-align:stretch;
-webkit-box-align:stretch;
align-items:stretch
}
.alignBaseline-4enZzv{
-ms-flex-align:baseline;
-webkit-box-align:baseline;
align-items:baseline
}
.justifyStart-2yIZo0{
-ms-flex-pack:start;
-webkit-box-pack:start;
justify-content:flex-start
}
.justifyEnd-1ceqOU{
-ms-flex-pack:end;
-webkit-box-pack:end;
justify-content:flex-end
}
.justifyCenter-29N31w{
-ms-flex-pack:center;
-webkit-box-pack:center;
justify-content:center
}
.justifyBetween-1d1Hto{
-ms-flex-pack:justify;
-webkit-box-pack:justify;
justify-content:space-between
}
.horizontal-2VE-Fw>.spacer-2Aeq3k,.horizontalReverse-k5PqxT>.spacer-2Aeq3k,.vertical-3X17r5>.spacer-2Aeq3k{
min-height:1px
}
.horizontal-2BEEBe>.flex-lFgbSz,.horizontal-2BEEBe>.flexChild-1KGW5q{
margin-left:10px;
margin-right:10px
}
.horizontal-2BEEBe>.flex-lFgbSz:first-child,.horizontal-2BEEBe>.flexChild-1KGW5q:first-child{
margin-left:0
}
.horizontal-2BEEBe>.flex-lFgbSz:last-child,.horizontal-2BEEBe>.flexChild-1KGW5q:last-child{
margin-right:0
}
<div class="flex-lFgbSz flex-3B1Tl4 horizontal-2BEEBe horizontal-2VE-Fw flex-3B1Tl4 directionRow-yNbSvJ justifyCenter-29N31w alignStretch-1hwxMa noWrap-v6g9vO private-channel-call-actions" style="">
<div class="joinVideoCallButton-2Pohj0">
<div class="underlay-1LCk7B" style="opacity: 1; transform: translateX(31.5781px);"></div>
<div class="flex-lFgbSz flex-3B1Tl4 horizontal-2BEEBe horizontal-2VE-Fw flex-3B1Tl4 directionRow-yNbSvJ justifyCenter-29N31w alignCenter-3VxkQP noWrap-v6g9vO inner-1e8n63" style="flex: 1 1 auto;">
<div class="flex-lFgbSz flex-3B1Tl4 horizontal-2BEEBe horizontal-2VE-Fw flex-3B1Tl4 directionRow-yNbSvJ justifyCenter-29N31w alignCenter-3VxkQP noWrap-v6g9vO callButton-205P-D" style="flex: 1 1 auto;"><img class="icon-3tOP24" src="http://discordapp.com/assets/0682cf612d4fed39efb57e0a1bcc8544.svg">
<div class="buttonText-1b8lyZ">Video</div>
</div>
<div class="callDivider-_hMb9n"></div>
<div class="flex-lFgbSz flex-3B1Tl4 horizontal-2BEEBe horizontal-2VE-Fw flex-3B1Tl4 directionRow-yNbSvJ justifyCenter-29N31w alignCenter-3VxkQP noWrap-v6g9vO callButton-205P-D" style="flex: 1 1 auto;">
<div class="buttonText-1b8lyZ">Voice</div><img class="icon-3tOP24" src="http://discordapp.com/assets/8b47456a037cc496c55ae8f871274018.svg"></div>
</div>
</div>
</div>
Part 1
The idea is fairly simple: you need to add an extra "hover box" element inside the button row, absolute position it, and finally just change the left value of the object according to the position of the cursor:
function relocateHoverBox(e) {
// Get all the needed values
// (idealy don't use as many variables)
let hoverBoxEl = e.target.closest(".btn-row").querySelector(".btn-row--hover-box");
let hoverBoxHalfWidth = hoverBoxEl.clientWidth / 2;
let containerX = e.target.closest(".btn-row").getBoundingClientRect().left;
let maxLeft = e.target.closest(".btn-row").clientWidth - hoverBoxEl.clientWidth;
let newLeftValue = e.pageX - containerX - hoverBoxHalfWidth;
// Apply new left value accordingly
if (newLeftValue >= 0) {
if (newLeftValue > maxLeft) {
hoverBoxEl.style.left = maxLeft + "px";
}
else {
hoverBoxEl.style.left = e.pageX - containerX - hoverBoxHalfWidth + "px";
}
}
else {
hoverBoxEl.style.left = 0 + "px";
}
}
// Set the hover box in the right position as soon as the mouse enters
const mouseoverHandler = function(e) {
relocateHoverBox(e);
}
// Update the hover box position
const mousemoveHandler = function(e) {
relocateHoverBox(e);
}
// Reset the hover box position
const mouseleaveHandler = function(e) {
e.target.closest(".btn-row").querySelector(".btn-row--hover-box").style.left = "";
}
document.getElementsByClassName("btn-row")[0].addEventListener('mousemove', mousemoveHandler);
document.getElementsByClassName("btn-row")[0].addEventListener('mouseenter', mouseoverHandler);
document.getElementsByClassName("btn-row")[0].addEventListener('mouseleave', mouseleaveHandler);
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: #202225;
}
.btn-row {
display: flex;
justify-content: center;
align-items: center;
border-radius: 4px;
height: 48px;
background: #43b581;
position: relative;
}
.btn-row--hover-box {
width: 50%;
height: 44px;
border-radius: 2px;
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
}
.btn-row--hover-box:hover {
background: rgba(255, 255, 255, 0.25);
}
.btn {
background: transparent;
letter-spacing: .1rem;
padding: 0 1rem;
border: 0;
color: #fff;
height: 28px;
display: block;
font-weight: bold;
text-transform: uppercase;
}
<div class="btn-row">
<div class="btn-row--hover-box"></div>
<button class="btn">Option 1</button>
<button class="btn">Option 2</button>
</div>
Part 2
Also, I wonder if it is possible without the use of Jquery?
If by "Jquery" you actually mean the jQuery library, then just remember that it is a library, which means that it is only a collection of useful scripts written in a programming language, then since you can write your own scripts using that language, it is possible to do it without jQuery
If by "Jquery" you mean Javascript (the language in which jQuery is written), then it's not possible to do it without it, since you need it to follow the x-coordinate of the cursor.

Categories

Resources