Categories
discuss

Removing the delay on text animation – HTML/CSS

I have the following code:

/*Vertical Flip*/

.verticalFlip {
  display: inline;
}

.verticalFlip span {
  animation: vertical 5s linear infinite 0s;
  -ms-animation: vertical 5s linear infinite 0s;
  -webkit-animation: vertical 5s linear infinite 0s;
  color: #ea1534;
  opacity: 0;
  overflow: hidden;
  position: absolute;
}

.verticalFlip span:nth-child(2) {
  animation-delay: 2.5s;
  -ms-animation-delay: 2.5s;
  -webkit-animation-delay: 2.5s;
}


/*Vertical Flip Animation*/

@-moz-keyframes vertical {
  0% {
    opacity: 0;
  }
  5% {
    opacity: 0;
    -moz-transform: rotateX(180deg);
  }
  10% {
    opacity: 1;
    -moz-transform: translateY(0px);
  }
  25% {
    opacity: 1;
    -moz-transform: translateY(0px);
  }
  30% {
    opacity: 0;
    -moz-transform: translateY(0px);
  }
  80% {
    opacity: 0;
  }
  100% {
    opacity: 0;
  }
}

@-webkit-keyframes vertical {
  0% {
    opacity: 0;
  }
  5% {
    opacity: 0;
    -webkit-transform: rotateX(180deg);
  }
  10% {
    opacity: 1;
    -webkit-transform: translateY(0px);
  }
  25% {
    opacity: 1;
    -webkit-transform: translateY(0px);
  }
  30% {
    opacity: 0;
    -webkit-transform: translateY(0px);
  }
  80% {
    opacity: 0;
  }
  100% {
    opacity: 0;
  }
}

@-ms-keyframes vertical {
  0% {
    opacity: 0;
  }
  5% {
    opacity: 0;
    -ms-transform: rotateX(180deg);
  }
  10% {
    opacity: 1;
    -ms-transform: translateY(0px);
  }
  25% {
    opacity: 1;
    -ms-transform: translateY(0px);
  }
  30% {
    opacity: 0;
    -ms-transform: translateY(0px);
  }
  80% {
    opacity: 0;
  }
  100% {
    opacity: 0;
  }
}


/* text */

#hero h1 {
  margin: 0;
  font-size: 64px;
  font-weight: 700;
  line-height: 56px;
  color: transparent;
  background: url("https://lovelytab.com/wp-content/uploads/2019/01/Tumblr-Aesthetic-Wallpapers-Free.jpg") repeat;
  background-clip: text;
  -webkit-background-clip: text;
}
<section id="hero">
  <h1 style="margin-bottom: 16px">Sample
    <div class="verticalFlip"><span>Change</span><span>Text</span></div>
  </h1>
</section>

This is working fine, however, how can I remove the delay? There is a long pause after the text alternating gets completed. I would like it to be instant where it keeps on alternating the text in a smooth manner. The only thing I would like to remove is the long pause, that is it. I am not sure which css property is causing that issue. Any suggestions?

Answer

You need to play around with the animation duration and also adjust the key frames percentages -> the tween in when you are flipping the text using your transform/opacity rules.

I have slightly adjusted each, but this comes down to a taste in how you want it to look and feel. Spreading the flipping animation over more of a percent (your tween) will lessen the amount of time you have in a pause between animations.

/*Vertical Flip*/

.verticalFlip {
  display: inline;
}

.verticalFlip span {
  animation: vertical 3.4s linear infinite 0s;
  -ms-animation: vertical 3.4s linear infinite 0s;
  -webkit-animation: vertical 3.4s linear infinite 0s;
  color: #ea1534;
  opacity: 0;
  overflow: hidden;
  position: absolute;
}

.verticalFlip span:nth-child(2) {
  animation-delay: 2s;
  -ms-animation-delay: 2s;
  -webkit-animation-delay: 2s;
}


/*Vertical Flip Animation*/

@-moz-keyframes vertical {
  0% {
    opacity: 0;
  }
  15% {
    opacity: 0;
    -moz-transform: rotateX(180deg);
  }
  35% {
    opacity: 1;
    -moz-transform: translateY(0px);
  }
  55% {
    opacity: 1;
    -moz-transform: translateY(0px);
  }
  80% {
    opacity: 0;
    -moz-transform: translateY(0px);
  }
  90% {
    opacity: 0;
  }
  100% {
    opacity: 0;
  }
}

@-webkit-keyframes vertical {
  0% {
    opacity: 0;
  }
  15% {
    opacity: 0;
    -webkit-transform: rotateX(180deg);
  }
  35% {
    opacity: 1;
    -webkit-transform: translateY(0px);
  }
  55% {
    opacity: 1;
    -webkit-transform: translateY(0px);
  }
  80% {
    opacity: 0;
    -webkit-transform: translateY(0px);
  }
  95% {
    opacity: 0;
  }
  100% {
    opacity: 0;
  }
}

@-ms-keyframes vertical {
  0% {
    opacity: 0;
  }
  15% {
    opacity: 0;
    -ms-transform: rotateX(180deg);
  }
  35% {
    opacity: 1;
    -ms-transform: translateY(0px);
  }
  55% {
    opacity: 1;
    -ms-transform: translateY(0px);
  }
  80% {
    opacity: 0;
    -ms-transform: translateY(0px);
  }
  95% {
    opacity: 0;
  }
  100% {
    opacity: 0;
  }
}


/* text */

#hero h1 {
  margin: 0;
  font-size: 64px;
  font-weight: 700;
  line-height: 56px;
  color: transparent;
  background: url("https://lovelytab.com/wp-content/uploads/2019/01/Tumblr-Aesthetic-Wallpapers-Free.jpg") repeat;
  background-clip: text;
  -webkit-background-clip: text;
}
<section id="hero">
  <h1 style="margin-bottom: 16px">Sample
    <div class="verticalFlip"><span>Change</span><span>Text</span></div>
  </h1>
</section>
Categories
discuss

Better way to use useState hook for setting the boolean state in React

I’ve just started learning React and got to know about the useState hook. I came across two different ways for setting the state for boolean data. So are these two approaches identical and if not, which one is one should prefer?

const [isChanged, setIsChanged] = useState<boolean>(false)
  
const onClick = () => {
    setIsChanged((prevState) => !prevState)  // Approach 1
    setIsChanged(!isChanged)  // Approach 2
}

Answer

Since, as often in code, a simple example paints a thousand words, here’s a simple CodeSandbox demo to illustrate the difference, and why, if you want an update based on the value of the state at the point of update, the “updater function” (Approach 1) is best:

https://codesandbox.io/s/stack-overflow-demo-nmjiy?file=/src/App.js

And here’s the code in a self-contained snippet:

<div id="root"></div><script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/standalone@7.16.7/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">

function App() {
  const [count, setCount] = React.useState(0);

  // this uses the "good way" but it doesn't really matter here
  const incrementPlain = () => setCount((oldCount) => oldCount + 1);

  const incrementWithTimeoutBad = () =>
    setTimeout(() => setCount(count + 1), 3000);
  const incrementWithTimeoutGood = () =>
    setTimeout(() => setCount((oldCount) => oldCount + 1), 3000);

  return (
    <div>
      <div>Current count: {count}</div>
      <div>
        <button onClick={incrementPlain}>
          Increment (doesn't matter which way)
        </button>
      </div>
      <div>
        <button onClick={incrementWithTimeoutBad}>
          Increment with delay (bugged)
        </button>
        <button onClick={incrementWithTimeoutGood}>
          Increment with delay (good)
        </button>
      </div>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));

</script>

Here we have a simple numeric “count” state which is displayed in the markup, together with 3 different buttons that all increment it.

The one on top just does a direct increment – I happen to have used the function form (“approach 1”) here because I prefer this style for reasons that will hopefully become clear, but as my comment says, it doesn’t actually matter here.

The two below use the two different approaches you outline in the question, and do so after a delay. I’ve done this with setTimeout here just to be simple – while this isn’t particularly realistic, similar effects are commonly seen in real apps where actions call an API endpoint (and even though one hopes that doesn’t normally take as long as 3 seconds, the same problems can always be observed with quicker requests – I’ve just slowed it down to be easier to trigger the bug).

To see the difference, try the following with each of the 2 bottom buttons:

  • click the button
  • click the button on top (to increment the count again) BEFORE the 3-second timeout is up

You should see a clear difference in behaviour:

  • with “approach 1” (button on the right, which I’m calling “good” here), the count increments a second time after the timeout is finished.
  • with “approach 2” (button on the left, which I’ve called “bugged”), there is no further increment from the value produced by the intermediate click on the top button, no matter how long you wait

(You can see this more dramatically if you click the bottom button multiple times quickly, then the top one once. And for an even more counterintuitive effect, try pressing the “bugged” bottom button one or more times, then clicking the top button more than once, all within the 3 second time interval.)

Why does this happen? Well, the “buggy” behaviour happens because the function inside the setTimeout is a closure over the outer variable count which is in the scope of the full component function. That means that when it’s called with count + 1 as the argument, it will update the count to 1 more than whatever it was at the point the function was defined. Say you do the above sequence from first loading the component where count is 0, then the more detailed sequence of what happens is:

  • the bottom button click schedules a callback to happen in 3 seconds’ time. Since count at that point is equal to 0, its argument, count + 1 is equal to 1.
  • the top button click rerenders the component, with count now equal to 1.
  • the callback set up at the first step later triggers, and sets the count to 1. This doesn’t cause any noticeable chance, because the count was already 1. (If you tried clicking the top button multiple times, so it now shows 2 or more, this will actually decrement the counter, because as I’m explaining, it will always get set to 1.)

If you know a little bit about JS closures, you might wonder why the count that is accessed in the closure is still 0. Wasn’t it previously updated to 1? No, it wasn’t, and that’s the bit that might be counterintuitive. Notice how count is declared with const? That’s right – it never actually changes. The reason the UI updates is because setCount causes React to rerender your component, which means the whole outer function corresponding to the component is called again. This sets up a whole new environment, with a new count variable. React’s internals ensure that the useState call now gives back 1 for the current count, which is therefore the value in that new “instance” of the component – but that’s irrelevant from the point of view of the function that was put in the event queue to fire after 3 seconds. As far as it’s concerned, the count variable – no longer in scope but “remembered” inside that callback as all closed-over variables are – has never changed from 0. The count that’s equal to 1 is in a different scope entirely, and forever inaccessible to that first callback.

How does the function argument form – “approach 1” – get round this? Very easily. It doesn’t hold any closure at all – the variable inside that function, which I’ve called oldCount here for the sake of both accuracy and to disambiguate from the outer count – has nothing to do with the count outside. It’s the argument to a function that React itself will call internally. When React does call the function, it always supplies the “most up-to-date” state value it has. So you don’t have to worry about “stale closures” or anything like that – you’re saying “whatever the most recent value was, update the count to be one more than that”, and React will take care of the rest.

I’ve called approach 2 “bugged” here because I think it’s reasonable to expect an increment to happen after the timeout, if you’ve clicked a button that you’ve set up to do an increment. But this isn’t always what you want. If you genuinely wanted the update to be based on the value at the point the button was first clicked, then of course you will prefer Approach 2, and Approach 1 will seem bugged. And in a sense that’s more often the case. I highly recommend reading this post by Dan Abramov – one of the core React developers – that explains a crucial difference between class components and functions, that’s based on many of the same arguments about closures that came into play here, where normally you do want the event handlers to reference values as they were at the time of render, not when they actually fire after an API request or timeout.

But that post doesn’t have anything to do with the “approach 1” form of state-updating functions, which isn’t even mentioned in the article. That’s because it’s irrelevant to the examples given – there’d be no (sensible) way to rewrite those examples to use it. But when you do want to update a state value based on its previous value – as could happen with negating a boolean value, as in the OP example, or incrementing a counter as in mine, I would argue that it’s more natural that you always want that “previous value” to be up to date. There are 2 buttons which both should increment a value, albeit in different ways – I think it’s reasonable to call it bugged if clicking both of them, depending on the timing, may only increment once in total.

But that’s of course up to each individual component or application to decide. What I hope I’ve done here is explain what the difference is, and give you a basis to choose which might be best. But I do believe that 90+% of the time, if you have the option of using the function argument (“approach 1”), it will be better, unless you know it isn’t.

Categories
discuss

Vue leaflet doesn’t work when building app

I’m currently creating a vue3 cli app that uses vue-leaflet (the vue3 compatible version)

Everything works great on my local dev environment but once my app is built the map doesn’t load, even when I resize like this thread explains well.

I tried using the leafletObject.invalidateSize() method but nothing changed.

My map is a component called using a v-if on first call (switch between a list view and the map) and a v-show once it has been initialized

  <transition name="fade" mode="out-in">
    <Map v-if="mapInit"
         v-show="(!mobileView && !listing) ||
          (mobileView && !listing && !getFiltresMobile)"
         :liste="getFilteredList"/>
  </transition>

Here is what I get on my dev version when I use npm run serve enter image description here

Here is the result on the prod version lealfet prod

Here is the relevant part of my code, I can add more if you think more code needs to be shown

<template>

  <div id="map">
    <l-map v-if="!loading" :style="mapHeight" :zoom="zoom" :center="centerValue" :options="{tap : false}" ref="map">
      <l-tile-layer :url="url"></l-tile-layer>
      <l-marker v-for="(marqueur) in mapData" :key="marqueur.id" :visible="isFiltered(marqueur)" :lat-lng="marqueur.latLng" :ref="setMarkerRefs" @click="markerClick(marqueur.latLng)">
        <l-icon :icon-url="checkActive(marqueur.latLng)" :icon-size="[30 , 37]" :popup-anchor="popupUpPosition" :icon-anchor="[15, 37]"/>
        <l-popup :options="popupoptions">
          <MarqueurPopup :contenu="marqueur"/>
        </l-popup>
      </l-marker>
    </l-map>
    <Loader v-if="loading"/>
  </div>

</template>

JS

<script>
import "leaflet/dist/leaflet.css"
import { LMap , LTileLayer , LPopup , LMarker , LIcon } from "@vue-leaflet/vue-leaflet";

export default {
  name: "Map",
  props : ['liste'],
  components: {
    Loader,
    MarqueurPopup,
    LMap,
    LTileLayer,
    LMarker,
    LPopup,
    LIcon,
  },
  data() {
    return {
      url: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png',
      zoom: 13,
      mapData : [],
      markerRefs : [],
      markerRefsDef : [],
      popupoptions : {
        maxWidth : 1000,
        minWidth : 300,
        closeButton : false,
      }
    };
  },

  beforeMount() {
    this.initMap()
  },
  mounted() {
    setTimeout( () => {
      console.log(this.$refs.map.leafletObject) // returns the leaflet map objects
      this.$refs.map.leafletObject.invalidateSize(); // no error but does nothing 
    }, 1000);
  },
  computed : {
    popupUpPosition() {
      if (window.innerWidth < 768) {
        return [0, 74]
      }
      return [0, -37]
    },
    mapHeight() {
      let colonneValue = window.innerWidth / 12;
      if (colonneValue < 115) {
        colonneValue = 115
      }
      let height = window.innerHeight - colonneValue;

      return {height : height+'px'}
    },
  },
  methods : {
    async initMap() {
      this.$store.commit('changeLoading' , true);
      this.mapData = []
      this.mapData = this.getFilteredList
      this.$store.commit('changeLoading' , false);
    },
  }
}
</script>

Is there a way to check if the invalidateSize() methods triggers ? My console.log get no errors but nothing changes so maybe it doesn’t trigger the method ?

Answer

Rather looks like the Leaflet CSS is incorrectly loaded in your production bundle: tiles are scrambled up, no zoom and attribution controls.

Source: stackoverflow
Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. By using this site, you agree to the Privacy Policy, and Copyright Policy. Content is available under CC BY-SA 3.0 unless otherwise noted. The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 © No Copyrights, All Questions are retrived from public domain..