Problem
In my app I have card with projects and sometimes images of those projects doesn't load on mobile i have to reload browser and to it is loaded correctly I have no idea how.I must also point out that on the computer viewer everything works correctly.
Stack
I`m using Astro.js with Preact.
Here is demonstration video of my problem
https://streamable.com/okquzk
My code
The way i use it. In Astro framework you can create collections with you mdx or md data here i pass route to imgs like this:
Note: in Astro you dont have to write public/something it ok to write
/projects/bhn.webp
---
title: "Black Hat News"
heroImage: /projects/bhn.webp
___
After some step I am using it inside Card.tsx
import "./styles/card.css";
type Props = {
title: string;
heroImage: string;
slug: string;
};
export const Card = ({ title, heroImage, slug }: Props) => (
<a href={slug}>
<div class="card">
<img src={heroImage} alt={title} />
<div class="info">
<h1>{title}</h1>
</div>
</div>
</a>
);
and there is css
/* Set padding top to make a trick with aspect ratio 16:9 */
.card {
padding: 1rem;
width: 100%;
padding-top: 56.25%;
position: relative;
display: flex;
align-items: flex-end;
transition: 0.4s ease-out;
box-shadow: 0px 7px 10px rgba(0, 0, 0, 0.5);
cursor: pointer;
}
.card:before {
content: "";
position: absolute;
top: 0;
left: 0;
display: block;
width: 100%;
height: 100%;
/* background: rgba(0, 0, 0, 0.6); */
z-index: 2;
}
.card img {
width: 100%;
height: 100%;
-o-object-fit: cover;
object-fit: cover;
object-position: center top;
position: absolute;
top: 0;
left: 0;
}
.card .info {
position: relative;
z-index: 3;
color: var(--color-text);
background: var(--color-dark);
padding: 0.4rem;
border-radius: 5px;
}
.card .info h1 {
font-size: var(--font-lg);
}
/* for desktop add nice effects */
#media (min-width: 1024px) {
.card:hover {
transform: translateY(10px);
}
.card:hover:before {
opacity: 1;
background: rgba(0, 0, 0, 0.6);
}
.card:hover .info {
opacity: 1;
transform: translateY(0px);
}
.card:before {
transition: 0.3s;
opacity: 0;
}
.card .info {
opacity: 0;
transform: translateY(30px);
transition: 0.3s;
}
.card .info h1 {
margin: 0px;
}
}
Edit
Added usage
import "./styles/projectsGrid.css";
import { useStore } from "#nanostores/preact";
import { getProjectsByTag } from "#utils/*";
import type { CollectionEntry } from "astro:content";
import { tagValue } from "src/tagStore";
import { Card } from "./Card";
type Props = {
projects: CollectionEntry<"projects">[];
};
export const ProjectsGrid = ({ projects }: Props) => {
const $tagValue = useStore(tagValue);
const filteredProjects = getProjectsByTag(projects, $tagValue);
return (
<div class="projects_wrapper">
{filteredProjects.map(({ data: { title, heroImage }, slug }) => (
<Card title={title} heroImage={heroImage} slug={slug} />
))}
</div>
);
};
Related
I have a navigation bar that has a logo and a burgerbar that changes from a burger bar to an "x" when opened.
The problem is that the dropdown should go down when you click on the burger and go back up when you press the "x". This functions smoothly and perfectly so far, but the problem I noticed is that when I resize the browser window from >700px to <700px, the dropdown menu animation going up occurs.
import React, { useState, useRef, useEffect } from "react";
import { BurgerBar } from "../../styles/Navbar.style";
import Navlinks from "./Navlinks";
function Burger() {
const [open, setOpen] = useState(false);
const [width, setWidth] = useState(window.innerWidth);
const prevWidth = useRef();
useEffect(() => {
prevWidth.current = width;
}, [width]);
function checkResize() {
setWidth(window.innerWidth);
if (prevWidth.current <= 700 && width > 700) {
setOpen(false);
};
};
window.addEventListener("resize", checkResize);
return (
<>
<BurgerBar open={open} onClick={() => {setOpen(!open);}}>
<span />
<span />
<span />
</BurgerBar>
<Navlinks open={open} />
</>
)
};
export default Burger;
This is my styled-components file with the relevant styles:
export const BurgerBar = styled.span`
z-index: 1;
width: 2rem;
position: fixed;
display: flex;
justify-content: space-around;
flex-flow: column;
top: ${({open}) => open ? 22 : 24}px;
height: ${({open}) => open ? 2.3 : 2}rem;
right: ${({open}) => open ? 17 : 20}px;
span {
width: 2rem;
height: 0.25rem;
background-color: rgb(46, 203, 64);
border-radius: 10px;
transform-origin: 1px;
&:nth-child(1) {
transform: ${({open}) => open ? 'rotate(45deg)' : 'rotate(0)'};
width: ${({open}) => open ? 2.3 : 2}rem;
}
&:nth-child(2) {
transform: ${({open}) => open ? 'translateX(100%)' : 'translateX(0)'};
opacity: ${({open}) => open ? 0 : 1};
}
&:nth-child(3) {
transform: ${({open}) => open ? 'rotate(-45deg)' : 'rotate(0)'};
width: ${({open}) => open ? 2.3 : 2}rem;
}
}
&:hover {
opacity: 0.7;
cursor: pointer;
}
#media (min-width: 701px) {
visibility: hidden;
}
`;
export const Dropdown = styled.ul`
z-index: 1;
position: fixed;
list-style: none;
display: flex;
flex-flow: row nowrap;
padding: 0;
margin: 0;
top: 0;
li {
padding: 20px 20px;
&:hover {
opacity: 0.7;
cursor: pointer;
}
}
#media (max-width: 700px) {
position: fixed;
z-index: 0;
flex-flow: column nowrap;
background-color: rgb(32, 80, 36);
transform: ${({ open }) => (open) ? 'translateY(0)' : 'translateY(-100%)'};
vertical-align: middle;
transition: transform 0.3s ease-in-out;
top: 85px;
align-items: center;
border-bottom-left-radius: 4%;
li {
padding-top: 10px;
padding-bottom: 4px;
}
}
`;
I recognize that the problem lies in these two lines:
transform: ${({ open }) => (open) ? 'translateY(0)' : 'translateY(-100%)'};
and
transition: transform 0.3s ease-in-out;
I'm new to Javascript and React though and I cannot think of an efficient way to block the transition animation from going off upon browser resizing.
I can link my repo if more code/reference is necessary.
I have added a button on my site which let's the users change to dark or light mode whenever they want. I added the button with a moon icon on it, but the problem is that I want that the moon icon changes to sun icon when the user is in dark mode. And changes to moon icon when user is in light mode.
function myfunction(e) {
console.log("you clicked");
document.documentElement.classList.toggle("dark-mode");
document.querySelectorAll(".inverted").forEach((result) => {
result.classList.toggle("invert");
});
}
const btn = document.querySelector('.btn')
btn.addEventListener('click', myfunction);
.dark-mode {
filter: invert(1) hue-rotate(180deg);
}
.invert {
filter: invert(1) hue-rotate(180deg);
}
<button class="btn"><img src='moon.png'></img></button>
The .inverted class in js is because I don't want the images to invert their colors.. so I gave all the images a class='inverted'
So, this is what I've done and someone please let me know how I should change the icon to moon and sun depending on the current mode (light or dark)
Thanks!
You could add the sun as another image to the button and change the visibility of the two images via your .dark-mode css class.
So whenever the .dark-mode class is present the moon gets hidden and the sun gets shown.
function myfunction(e) {
console.log("you clicked");
document.documentElement.classList.toggle("dark-mode");
document.querySelectorAll(".inverted").forEach((result) => {
result.classList.toggle("invert");
});
}
const btn = document.querySelector('.btn')
btn.addEventListener('click', myfunction);
.dark-mode {
filter: invert(1) hue-rotate(180deg);
}
.invert {
filter: invert(1) hue-rotate(180deg);
}
/* button handling */
.moon {
display: block;
}
.sun {
display: none;
}
.dark-mode .moon {
display: none;
}
.dark-mode .sun {
display: block;
}
<button class="btn">
<img class="moon" src="moon.png" alt="moon"></img>
<img class="sun" src="sun.png" alt="sun"></img>
</button>
You could go with the CSS approach as in #Fabian's answer. If you would like to go with JS, you could simply use a flag to indicate whether or not the user switched to dark mode, and dynamically set the icon based on it.
let isDarkMode = document.documentElement.classList.contains("dark-mode");
function myfunction(e) {
document.documentElement.classList.toggle("dark-mode");
document.querySelectorAll(".inverted").forEach((result) => {
result.classList.toggle("invert");
});
e.currentTarget.querySelector("img").src = isDarkMode ? "sun.png" : "moon.png";
}
You can use the below reference for the toggle button from light mode to dark mode and dark mode to light mode.
body {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.toggle-checkbox {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.toggle-slot {
position: relative;
height: 10em;
width: 20em;
border: 5px solid #e4e7ec;
border-radius: 10em;
background-color: white;
box-shadow: 0px 10px 25px #e4e7ec;
transition: background-color 250ms;
}
.toggle-checkbox:checked ~ .toggle-slot {
background-color: #374151;
}
.toggle-button {
transform: translate(11.75em, 1.75em);
position: absolute;
height: 6.5em;
width: 6.5em;
border-radius: 50%;
background-color: #ffeccf;
box-shadow: inset 0px 0px 0px 0.75em #ffbb52;
transition: background-color 250ms, border-color 250ms, transform 500ms cubic-bezier(.26,2,.46,.71);
}
.toggle-checkbox:checked ~ .toggle-slot .toggle-button {
background-color: #485367;
box-shadow: inset 0px 0px 0px 0.75em white;
transform: translate(1.75em, 1.75em);
}
.sun-icon {
position: absolute;
height: 6em;
width: 6em;
color: #ffbb52;
}
.sun-icon-wrapper {
position: absolute;
height: 6em;
width: 6em;
opacity: 1;
transform: translate(2em, 2em) rotate(15deg);
transform-origin: 50% 50%;
transition: opacity 150ms, transform 500ms cubic-bezier(.26,2,.46,.71);
}
.toggle-checkbox:checked ~ .toggle-slot .sun-icon-wrapper {
opacity: 0;
transform: translate(3em, 2em) rotate(0deg);
}
.moon-icon {
position: absolute;
height: 6em;
width: 6em;
color: white;
}
.moon-icon-wrapper {
position: absolute;
height: 6em;
width: 6em;
opacity: 0;
transform: translate(11em, 2em) rotate(0deg);
transform-origin: 50% 50%;
transition: opacity 150ms, transform 500ms cubic-bezier(.26,2.5,.46,.71);
}
.toggle-checkbox:checked ~ .toggle-slot .moon-icon-wrapper {
opacity: 1;
transform: translate(12em, 2em) rotate(-15deg);
}
<head>
<script src="https://code.iconify.design/1/1.0.4/iconify.min.js"> </script>
</head>
<label>
<input class='toggle-checkbox' type='checkbox'>
<div class='toggle-slot'>
<div class='sun-icon-wrapper'>
<div class="iconify sun-icon" data-icon="feather-sun" data-inline="false"></div>
</div>
<div class='toggle-button'></div>
<div class='moon-icon-wrapper'>
<div class="iconify moon-icon" data-icon="feather-moon" data-inline="false"></div>
</div>
</div>
</label>
function myfunction(e) {
const doc = document.documentElement
doc.classList.toggle("dark-mode");
document.querySelectorAll(".inverted").forEach((result) => {
result.classList.toggle("invert");
});
const img = e.currentTarget.querySelector('img')
const label = e.currentTarget.querySelector('span')
if (doc.classList.contains('dark-mode')) {
img.src = 'sun.png'
label.innerHTML = 'Light mode'
} else {
img.src = 'moon.png'
label.innerHTML = 'Dark mode'
}
}
const btn = document.querySelector('.btn')
btn.addEventListener('click', myfunction);
.dark-mode {
filter: invert(1) hue-rotate(180deg);
}
.invert {
filter: invert(1) hue-rotate(180deg);
}
'
<button class="btn">
<img src='moon.png' alt="" />
<span>Dark mode</span>
</button>
I am trying to a make carousel using pure Javascript. I successfully manage to slide the carousel and have created left and right buttons.
I took my slide functions and added them to the button on-click event-listener, but I have problems when I implement the function on my buttons. It does not behave as expected. My code is below, how can I fix this?
const images = document.getElementById('imgs'); //here
const allImages = document.querySelectorAll('#imgs img');
const leftBtn = document.getElementById('left');
const rightBtn = document.getElementById('right');
let index = 0;
function changeSliderPage() {
const dot = [...document.getElementsByClassName('star')];
index++;
if (index > allImages.length - 1) {
index = 0
}
imgs.style.transform = `translateX(${-index * 500}px)`;
dot.forEach((dot, i) => {
if (i === index) {
dot.classList.add('active')
} else {
dot.classList.remove('active')
}
});
};
allImages.forEach(i => {
const elem = document.createElement('div');
elem.classList.add('star');
document.body.appendChild(elem)
});
rightBtn.onclick = () => {
changeSliderPage(index + 1);
}
leftBtn.onclick = () => {
changeSliderPage(index - 1);
}
let x = setInterval(changeSliderPage, 100000);
images.onmouseover = () => {
clearInterval(x)
}
images.onmouseout = () => {
x = setInterval(changeSliderPage, 2000);
}
*{
box-sizing: border-box;
}
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.carousel {
overflow: hidden;
width: 500px;
height: 500px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, .3);
border-radius: 5px;
}
.image-container {
display: flex;
transition: transform 300ms linear;
transform: translateX(0);
}
img {
width:500px;
height: 500px;
object-fit: cover;
}
.star{
cursor: pointer;
height: 15px;
width: 15px;
margin: 0 10px;
border-radius: 50%;
display: inline-block;
transition: background-color 0.6s ease;
background-color: #eeeeee;
}
.star.active{
background-color: red;
}
button{
cursor: pointer;
position: relative;
font-size: 18px;
transition: 0.6s ease;
user-select: none;
height: 50px;
width: 40px;
display: flex;
justify-content: center;
align-items: center;
align-content: center;
top: calc(50% - 25px);
}
button:hover {
background-color: rgba(0,0,0,0.8);
};
button.left {
border-radius: 3px 0 0 3px;
right: 0;
}
button.left {
border-radius: 3px 0 0 3px;
left: 0;
}
<button id="left">❮</button>
<button id="right">❯</button>
<div class="carousel">
<div class="image-container" id="imgs" >
<img src="https://images.unsplash.com/photo-1599736375341-51b0a848f3c7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60" alt="">
<img src="https://images.unsplash.com/photo-1516026672322-bc52d61a55d5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60" alt="">
<img src="https://images.unsplash.com/photo-1573081586928-127ecc7948b0?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60" alt="">
<img src="https://images.unsplash.com/flagged/photo-1572850005109-f4ac7529bf9f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60" alt="">
</div>
</div>
Logic that I use with carousels:
for example you have 4 images:
[1][2][3][4]
I have an animation for sliding every image, I add 5th image which is same as image no 1:
[1][2][3][4][1]
Imagine cursor which shows what image is currently displayed, Ill mark cursor as ! !
So at begin:
[!1!][2][3][4][1]
Now the slider moves on...
[1][!2!][3][4][1]
etc...
It moves to last image:
[1][2][3][4][!1!]
And now it has to move under the hood from last image to first image, but without any animation so the whole change is not visible by user:
[!1!][2][3][4][5]
This way you can get inifinite carousel, just need to check in javascript if current image is last one and you want to slide right -> no animation. Same if you are on 1st image and want to slide left.
I'm trying to add a scale animation to an SVG. Its working fine in Webkit based browsers, but on iOS Chrome and Safari the animation is super slow. Here is the page I am trying to animate. Here is the relevant code...
const HomeHeading = styled.svg`
margin: 0;
color: rgba(255, 255, 255, 1);
height: 100vh;
width: 100vw;
position: fixed;
top: 0;
left: 0;
backface-visibility: hidden;
perspective: 1000;
transform: scale(
${props =>
props.scrollPosition / props.scale < 1
? 1
: props.scrollPosition / props.scale}
)
translateZ(0);
transform-origin: 42% 56%;
#media screen and (max-width: 480px) {
transform-origin: 43% 38% !important;
}
rect {
-webkit-mask: url(#mask);
mask: url(#mask);
fill: #f00;
}
defs {
mask {
rect {
fill: white;
}
text {
transform: translateY(10%);
font-size: 8vw;
#media screen and (max-width: 480px) {
transform: translateY(0);
}
&:last-child {
#media screen and (max-width: 480px) {
transform: translateY(0);
}
transform: translateY(20%);
}
}
}
}
`;
const HomeSubHomeSectionHeading = styled.section`
width: 100vw;
height: 100vh;
top: 0;
left: 0;
background: #1ecbe1;
position: fixed !important;
display: flex;
align-content: center;
justify-content: center;
flex-wrap: wrap;
`;
const ColorChanger = styled.div`
position: fixed;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
z-index: 3;
background-color: rgb(255, 255, 255);
opacity: ${props => 3000 / props.scrollPosition / 20};
`;
const ImageStamp = styled.div`
width: 280px;
height: auto;
display: inline-block;
padding: 10px;
background: white;
position: relative;
-webkit-filter: drop-shadow(0px 0px 10px rgba(0, 0, 0, 0.5));
background: radial-gradient(
transparent 0px,
transparent 4px,
white 4px,
white
);
background-size: 20px 20px;
background-position: -10px -10px;
&:after {
content: "";
position: absolute;
left: 5px;
top: 5px;
right: 5px;
bottom: 5px;
z-index: -1;
}
#media screen and (max-width: 768px) {
width: 200px;
margin-bottom: 30px;
}
`;
const MeSection = styled.section`
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
`;
const MePhoto = styled.div`
display: flex;
align-items: center;
justify-content: center;
flex: 1 0 33.333%;
`;
const MeBio = styled.div`
flex: 1 0 66.666%;
color: #ffffff;
padding: 0 30px;
`;
const MeSocials = styled.div`
svg {
margin: 0 10px;
}
`;
const SocialLink = styled.a`
color: #fff;
&:hover {
color: #f1f1f1;
}
`;
const IndexPage = props => {
const [scrollPosition, setScrollPosition] = useState(0);
const [scale, setScale] = useState(0);
useEffect(() => {
window.addEventListener("scroll", handleScroll, { passive: true });
});
const handleScroll = () => {
const position = window.scrollY;
setScale(document && document.width > 500 ? 20 : 5);
setScrollPosition(position);
};
return (
<Layout>
<HomeSubHomeSectionHeading>
<ColorChanger scrollPosition={scrollPosition}></ColorChanger>
<MeSection>
<div className="container">
<MePhoto>
<ImageStamp>
<Img fluid={props.data.mattImage.childImageSharp.fluid} />
</ImageStamp>
</MePhoto>
<MeBio>
<h3>Hi, I'm Matt!</h3>
<hr />
<p>
I'm a Lead Frontend Developer currently based at Oliver Wyman
Digital. I have experience in a range of frontend technologies
and practices; more recently dabbling with AB testing, VueCLI
and Typescript.
</p>
<p>
Outside of the web world, I like to run, travel and like to
watch movies. Apart from Toy Story 1, I cried when I found out
Buzz Lightyear couldn't fly.
</p>
<h4>Find out more</h4>
<hr />
<MeSocials>
<SocialLink
href="https://uk.linkedin.com/in/mattmaclennan"
target="_blank"
>
<FontAwesomeIcon icon={faLinkedin} />
</SocialLink>
<SocialLink href="https://github.com/mmaclenn" target="_blank">
<FontAwesomeIcon icon={faGithub} />
</SocialLink>
</MeSocials>
</MeBio>
</div>
</MeSection>
</HomeSubHomeSectionHeading>
<HomeHeading
preserveAspectRatio="xMinYMin meet"
scale={scale}
scrollPosition={scrollPosition}
>
<defs>
<mask id="mask" x="0" y="0" width="100%" height="100%">
<rect x="0" y="0" width="100%" height="100%" fill="#fff"></rect>
<text x="50%" y="40%" textAnchor="middle">
Matt Maclennan
</text>
<text id="editText" x="50%" y="45%" textAnchor="middle">
Web Developer
</text>
</mask>
</defs>
<rect
x="0"
y="0"
width="100%"
height="100%"
fill="#E1341E"
id="mask"
></rect>
</HomeHeading>
</Layout>
);
};
export const fluidImage = graphql`
fragment fluidImage on File {
childImageSharp {
fluid(maxWidth: 1000) {
...GatsbyImageSharpFluid
}
}
}
`;
export const pageQuery = graphql`
query {
mattImage: file(relativePath: { eq: "me.jpg" }) {
...fluidImage
}
}
`;
export default IndexPage;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I'm aware I can only animate transforms and opacity, also I've added translateZ to use hardware rendering and backface-visibility to the CSS with no luck.
Any ideas where I'm going wrong?
Edit: As per the comments, I have tried to throttle the scroll callback by using this package. Here is the code I'm using based on this package...
useScrollPosition(
({ prevPos, currPos }) => {
setScale(document && document.width > 500 ? 20 : 5);
const shouldBeStyle = {
transform: `scale(${
Math.abs(currPos.y) < 9 ? 1 : Math.abs(currPos.y) / scale
}) translateZ(0)`,
pointerEvents: `${Math.abs(currPos.y) > 1000 ? "none" : "auto"}`,
};
const opacityStyle = {
opacity: 1000 / Math.abs(currPos.y) / 20,
};
if (JSON.stringify(shouldBeStyle) === JSON.stringify(scrollStyling))
return;
setOpactiyStyling(opacityStyle);
setScrollStyling(shouldBeStyle);
},
[scrollStyling, opacityStyling]
);
useEffect(() => {
window.addEventListener("scroll", handleScroll, { passive: true });
});
This adds an event listener every time props change. You probably only want to do this once, e.g.:
useEffect(() => {
window.addEventListener("scroll", handleScroll, { passive: true });
}, []);
or at the very least put some dependencies in those brackets. See: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
You can use this, also you need to return a cleanup after the component is unmounted. I hope it helps
useEffect(() => {
window.addEventListener("scroll", handleScroll, { passive: true });
return () => {
window.removeEventListener("scroll", handleScroll, { passive: true });
};
}, []);
I have question about Vue.js. How can i fix this? I didn't find anything in documentation. I've got this error: "[vue/require-v-for-key]
Elements in iteration expect to have 'v-bind:key' directives."
And this: "Elements in iteration expect to have 'v-bind:key' directives."
I have this i my Roulette.vue
<template>
<div class="roulette">
<h1>Roulette</h1>
<div class="radio" v-for="genre in genres"> **here**
<input
#change="onGenrePick"
type="radio"
name="genre"
v-bind:id="genre.id"
v-bind:value="genre.id">
<label v-bind:for="genre.id">{{genre.name}}</label>
</div>
<Button class="btn" :onClick="roll">Roll</Button>
<MovieCard
v-if="!!movie"
v-bind:image="serviceGetImagePath(movie.poster_path)"
v-bind:title="movie.title"
v-bind:releaseDate="serviceFormatYear(movie.release_date)"
v-bind:id="movie.id"
v-bind:voteAverage="movie.vote_average"/>
</div>
</template>
<script>
import MovieCard from '../components/MovieCard'
import Button from '../components/Button'
import {movieService} from '../mixins/movieService'
export default {
name: 'Roulette',
components: {Button, MovieCard},
mixins: [movieService],
mounted: async function () {
this.genres = await this.serviceGetGenres()
},
data: () => ({
genres: [],
pickedGenres: new Set(),
movie: null
}),
methods: {
async roll() {
const genreIds = Array.from(this.pickedGenres)
const movies = await this.serviceGetMoviesByGenre(genreIds)
this.movie = movies[this.getRandom(movies.length)]
},
onGenrePick(e) {
this.pickedGenres.add(e.target.value)
},
getRandom(max) {
return Math.floor(Math.random() * max - 1)
}
}
}
</script>
<style scoped lang="scss">
.roulette {
margin: 40px;
}
.btn {
display: block;
min-width: 220px;
}
.radio {
display: inline-block;
margin: 20px 10px;
> label {
margin-left: 5px;
}
}
</style>
And in my UpcomingMovies.vue also
<template>
<div class="wrapper" v-if="movies.length">
<MovieCard
v-for="movie in movies" **here**
v-bind:image="serviceGetImagePath(movie.poster_path)"**here**
v-bind:title="movie.title"**here**
v-bind:releaseDate="serviceFormatYear(movie.release_date)"**here**
v-bind:id="movie.id"**here**
v-bind:voteAverage="movie.vote_average"/>**here**
<div class="loader">
<Button class="loader__btn" :onClick="loadNextPage">Load</Button>
</div>
<router-link :to="routes.roulette.path">
<div class="roulette">
<img src="../assets/roulette.png" alt="Roulette">
</div>
</router-link>
</div>
<Loader v-else/>
</template>
<script>
import Button from '../components/Button'
import MovieCard from '../components/MovieCard'
import Loader from '../components/Loader'
import { movieService } from '../mixins/movieService'
import routes from '../router/routes'
export default {
name: 'UpcomingMovies',
mixins: [movieService],
components: { Button, MovieCard, Loader },
data: () => ({
movies: [],
page: 1,
routes
}),
mounted() {
this.getMovies(this.page)
},
methods: {
getMovies: async function (page) {
const movies = await this.serviceGetMovies(page)
this.movies.push(...movies)
},
loadNextPage() {
this.getMovies(++this.page)
}
}
}
</script>
<style scoped lang="scss">
.wrapper {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
margin-top: 40px;
margin-bottom: 40px;
}
.loader {
width: 100%;
text-align: center;
margin-top: 40px;
margin-bottom: 40px;
&__btn {
border: 5px dashed white;
background-color: transparent;
border-radius: 50%;
width: 80px;
height: 80px;
font-weight: bold;
text-transform: uppercase;
transition: border-radius 100ms ease-in-out, width 120ms ease-in-out 120ms;
&:hover {
border-radius: 0;
background-color: rgba(white, 0.1);
width: 200px;
}
}
}
.roulette {
cursor: pointer;
position: fixed;
right: 25px;
bottom: 25px;
> img {
opacity: .8;
animation: rotate 5s infinite;
width: 70px;
height: auto;
transition: opacity 220ms linear;
&:hover {
opacity: 1;
}
}
}
#keyframes rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
</style>
Vue internally uses unique keys for each loop to determine which element to update at which point during an update process. Therefore every v-for needs a v-bind:key attribute to work properly. In your special case this would be as the following:
<div class="radio" v-for="genre in genres" v-bind:key="someUniqueId"> **here**
You can use the current loop index as ID or anything else.