How to render a self-destructing paragraph at mouse position in React? - javascript

I am trying to get a paragraph to appear at the location of the mouse coordinates, but self-destruct after 1 second.
$(function(){
var fadeDelay = 1000;
var fadeDuration = 1000;
$(document).click(function(e){
var div = $('<div class="image-wrapper">')
.css({
"left": e.pageX + 'px',
"top": e.pageY + 'px'
})
.append($('<img src="" alt="myimage" />'))
.appendTo(document.body);
setTimeout(function() {
div.addClass('fade-out');
setTimeout(function() { div.remove(); }, fadeDuration);
}, fadeDelay);
});
});
The code above is from a fiddle which represents the effect that I am looking for; however, it uses jQuery - while I am working with React.
I tried following this linear process:
1 - In the state, toggle a boolean with mouse clicks
playerAttack = () => {
this.setState({ hasPlayerAttacked: true })
}
2 - In a function, when the boolean is true, return a paragraph and set the boolean back to false
renderDamageDealtParagraph = () => {
if (this.state.hasPlayerAttacked) {
return <p>{this.state.playerAttack}</p>;
this.setState({ hasPlayerAttacked: false });
}
};
However, with this approach there were too many fallacies; main one being that upon resetting the boolean back to false, the rendered paragraph immediately disappears (instead of after a timeout of 1000ms).
What is the best wait to implement something like the linked fiddle, in ReactJS using vanilla JS?
Thanks in advance to whoever might be able to help.

You can basically do something like this:
Have state to track the mouse position x and y, and two booleans isShown and shouldHide to coordinate the disappering div
On click, show the div by setting isShown to true and immediately setTimeout to start hiding it in the future by adding a class by flipping the shouldHide to true
Once the class is added, the element will fade and will trigger the transitionend event at which point you can remove the div entirely by flipping the isShown to false and shouldHide to false boolean
Sample Implementation (Sorry for the shitty code, been a while since I React-ed)
JS Fiddle
class SelfDestructDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
x: 0,
y: 0,
isShown: false,
shouldHide: false
};
this.handleClick = this.handleClick.bind(this);
this.reset = this.reset.bind(this);
}
reset() {
this.setState({
x: 0,
y: 0,
isShown: false,
shouldHide: false
});
}
handleClick(event) {
if (this.state.isShown) {
return;
}
const { clientX, clientY } = event;
this.setState({
x: clientX,
y: clientY,
isShown: true,
shouldHide: false
});
setTimeout(() => {
this.setState({ shouldHide: true });
}, 1000);
}
render() {
const p = this.state.isShown && (
<div
onTransitionEnd={this.reset}
className={`${this.state.shouldHide ? "should-hide" : ""} modal`}
style={{ top: this.state.y, left: this.state.x }}
>
Self destructing....
</div>
);
return (
<div className="container" onClick={this.handleClick}>
{p}
</div>
);
}
}
ReactDOM.render(<SelfDestructDemo />, document.querySelector("#app"));

Related

Hide an element when scroll to specific class

I Used this JQuery code to have a sticky "Go to top" button:
//sticky back-to-top button
(function (t) {
t(window).bind("scroll", function () {
500 < t(this).scrollTop()
? t(".back-to-top").fadeIn(400)
: t(".back-to-top").fadeOut(400);
}),
t(".back-to-top").click(function () {
t("html, body").animate({ scrollTop: "0px" }, 500);
});
})(jQuery);
this code works correctly.
but i want when this sticky button reaches a specific class called "go-top", disappears. sorry for my bad english.
You can use interception observer so when the target element with said class is in view, it triggers the callback to hide the button.
Example: The code might not be exact but it is meant to point in the direction to follow.
let options = {
root: document.querySelector('.go-top'),
rootMargin: '0px',
threshold: 1.0
}
let prevRatio = 0.0;
let observer = new IntersectionObserver(callback, options);
let target = document.querySelector('#back-to-top-button');
observer.observe(target);
function handleIntersect(entries, observer) {
entries.forEach((entry) => {
if (entry.intersectionRatio > prevRatio) {
entry.target.style.display = "none";
} else {
entry.target.style.backgroundColor = "block";
}
prevRatio = entry.intersectionRatio;
});
}
Let me know how it goes...
You can switch the root to null but this makes the button disappear as soon as the element with the class is in the viewport

Smooth swipe up animation (React Native Animation)

What do I expect to do:
A modal screen that you can close by swiping up.
Inside the modal I have:
componentWillMount() {
this.animated = new Animated.Value(0);
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onPanResponderMove: (e, gestureState) => {
const { dy } = gestureState;
if(dy < 0) {
this.animated.setValue(dy);
}
},
onPanResponderRelease: (e, gestureState) => {
const { dy } = gestureState;
if(dy < -100) { // Swipe up away
Animated.timing(this.animated, {
toValue: -400,
duration: 150
}).start();
this.props.hideModal();
this.doSomething();
} else if(dy > -100 && dy < 0) { // Swipe back to initial position
Animated.timing(this.animated, {
toValue: 0,
duration: 150
}).start();
}
}
})
}
This Modal appears by clicking on a button in a parent component. By default in that parent there is a state value showModal : false. Opening the Modal this state is changed to true and when closing to false.
The main problem for now is that when you swipe for closing this Modal doesn't go up smoothly and disappear. It goes up to 100px, stops on a place and starts disappearing.
If remove this.props.hideModal() this Modal goes up to the top of a screen smoothly and disappears as I want, but it is not closed completely and you can not click on any other buttons after that.
Basically, I need to run this.props.hidModal() AFTER the animation has been finished.
How can I implement smooth closing of the Modal?
I hope I described my problem understandably.
If calling this.props.hideModal() after the animatiom is finished is the fix (which I believe it is) you could pass this as a callback to .start().
Animated.timing({..}).start(this.props.hideModal)

Handling Scroll in Reactjs

I'm building a React app, and one of the main features will be a full-screen title page. When the user scrolls on the title page, the page automatically scrolls down and the header bar sticks to the top. I do not want the body overflow hidden here, because as soon as I go to put the overflow back in, the scrollbar will appear and the whole page jolts left about 5px.
I've seen this feature a lot; it's pretty common for single-page web designs. However, I can't seem to get it right. I've gotten to a decent point where the app will scroll for you, but it always bugs out when I try to scroll in the middle of the automatic scroll. This is the closest I've gotten to a solution:
import React, {Component} from 'react';
import Scroll from 'react-scroll';
class Header extends Component {
constructor() {
super();
this.state = {
scrolling: false,
inLogo: false,
sticky: "",
lastScrollPos: 0
}
/* Must bind to access 'this' */
this.handleScroll = this.handleScroll.bind(this);
this.isScrollingDown = this.isScrollingDown.bind(this);
this.scroller = Scroll.animateScroll;
}
componentDidMount() {
window.addEventListener("scroll", this.handleScroll);
Scroll.Events.scrollEvent.register('end', function(to, element) {
this.state.scrolling = false;
if(!this.state.inLogo) {
this.setState({sticky: "sticky"});
}
}.bind(this));
if(window.pageYOffset < 150) {
this.state.inLogo = true;
this.scroller.scrollToTop();
} else {
this.state.inLogo = false;
this.scroller.scrollTo(window.innerHeight);
}
}
componentDidUpdate() {
if(this.state.scrolling) {
if(this.state.inLogo) {
this.scroller.scrollToTop();
} else {
this.scroller.scrollTo(window.innerHeight);
}
}
}
handleScroll(e) {
console.log(this.state.scrolling);
if(this.state.scrolling) {
return;
}
var scrollDown = this.isScrollingDown();
var inLogo = this.isInLogo();
if(inLogo) {
if(scrollDown) {
console.log("Scrolling down in logo");
this.setState({
scrolling: true,
inLogo: false
});
}
if(!scrollDown) {
console.log("Scrolling up in logo");
this.setState({
scrolling: true,
inLogo: true,
sticky: ""
});
}
}
}
isScrollingDown() {
var scrollingDown = window.pageYOffset > this.state.lastScrollPos;
this.state.lastScrollPos = window.pageYOffset;
return scrollingDown;
}
isInLogo() {
return window.pageYOffset > 150 && window.pageYOffset < window.innerHeight;
}
render() {
return (
<div id="app-header" className={"header "+this.state.sticky}>
<div className="header-filler"></div>
<button>Contact Us</button>
<button>First</button>
</div>
);
}
}
The problem here is that when you scroll in the middle of an automatic scroll, the "end" scroll event in react-scroll is fired. So, the callback for the "end" scroll even sets this.state.scrolling to false. Now, since that's false, the scroll is handled and I setState() again, potentially making the header bar sticky.
Bottom line: the ideal solution is to disable the scroll in componentDidUpdate when it begins scrolling, and enable it again in the "end" event handler. The problem with this solution is this event handler is called when the user interrupts an automatic scroll.
Lastly, I've found some methods on stackoverflow to disable/enable scrolling, but I took them out because they didn't seem to help the "end" event handling issue. It just re-enables the scrolling anyway when the handler is fired.
Please ask questions, I'm trying really hard to explain this clearly.
Found the solution. I had to basically "continue" the scroll in my 'end' event handler if it is called before the scroll is actually finished. Here is the updated code:
import React, { Component } from 'react';
import Scroll from 'react-scroll';
class Header extends Component {
constructor() {
super();
//scroll direction: (1) - down, (2) - up
this.state = {
scrolling: false,
inLogo: false,
sticky: "",
lastScrollPos: 0
}
/* Must bind to access 'this' */
this.handleScroll = this.handleScroll.bind(this);
this.scroller = Scroll.animateScroll;
this.isScrollingDown = this.isScrollingDown.bind(this);
}
componentDidMount() {
window.addEventListener("scroll", this.handleScroll);
Scroll.Events.scrollEvent.register('end', function() {
if(window.pageYOffset == 0) {
console.log("End: "+window.pageYOffset);
this.setState({sticky: "", scrolling: false});
}
else if(window.pageYOffset == window.innerHeight) {
console.log("End: "+window.pageYOffset);
this.setState({sticky: "sticky", scrolling: false});
} else {
if(this.state.inLogo) {
this.scroller.scrollToTop();
} else {
this.scroller.scrollTo(window.innerHeight);
}
}
}.bind(this));
if(window.pageYOffset < 150) {
this.state.inLogo = true;
this.scroller.scrollTo(0);
} else {
this.state.inLogo = false;
this.scroller.scrollTo(window.innerHeight);
}
}
componentDidUpdate() {
if(this.state.scrolling) {
if(this.state.inLogo) {
this.scroller.scrollToTop();
} else {
this.scroller.scrollTo(window.innerHeight);
}
}
}
handleScroll(e) {
console.log(this.state.scrolling);
if(this.state.scrolling) {
return;
}
var scrollDown = this.isScrollingDown();
var inLogo = this.isInLogo();
if(inLogo) {
if(scrollDown) {
console.log("Scrolling down in logo");
this.setState({
scrolling: true,
inLogo: false
});
}
if(!scrollDown) {
console.log("Scrolling up in logo");
this.setState({
scrolling: true,
inLogo: true,
sticky: ""
});
}
}
}
isScrollingDown() {
var scrollingDown = window.pageYOffset > this.state.lastScrollPos;
this.state.lastScrollPos = window.pageYOffset;
return scrollingDown;
}
isInLogo() {
return window.pageYOffset > 150 && window.pageYOffset < window.innerHeight - 5;
}

How to trigger a vue method only once, not every time

I'm handling a rotate even on change:
<div #change="handleRotate"></div>
<script>
export default {
data: {
rotate = 0
},
methods: {
handleRotate () {
this.rotate = this.rotate + this.getRotateAngle(e.clientX, e.clientY)
}
}
}
</script>
Right now, the second this.rotate runs on every change. How can I do it so that the second this.rotate is applied only the first time handleRotate runs?
Solving it Vue way:
You can use $once, which will listen for a event but only once.
Listen for a custom event, but only once. The listener will be removed once it triggers for the first time.
You just need to add .once to #change like following:
<div #change.once="handleRotate"></div>
<script>
export default {
//No Change here
}
</script>
Check demo if this in the fiddle.
Old Answer:
If you do not want to have initial value set for rotate, you can have one more variable : hasRotated to track whether rotate has been changed or not. Initially set hasRotated to true, once rotate has been changed set hasRotated to false, like following:
<div #change="handleRotate"></div>
<script>
export default {
data: {
rotate: 123,
hasRotated: false
},
methods: {
handleRotate () {
if(this.hasRotated){
this.rotate = this.rotate + this.getRotateAngle(e.clientX, e.clientY)
this.hasRotated = false
}
}
}
}
</script>
one simple solution would be to add a marker somewhat like this:
<script>
export default {
data: {
rotate = 0
},
methods: {
handleRotate () {
if(!this.rotated){
this.rotate = this.rotate + this.getRotateAngle(e.clientX, e.clientY);
this.rotated = true;
}
}
}
}
</script>
of course you would need to initiate this.rotated as false
If rotate start always at zero you can do:
export default {
data: {
rotate = 0
},
methods: {
handleRotate(e) {
if (this.rotate !== 0) {
return;
}
this.rotate = this.rotate + this.getRotateAngle(e.clientX, e.clientY);
}
}
};

React - Button Pressed, keep calling function

I'm trying to implement a zoom function. onClick works fine, but I'd like to have it when I hold the zoom button down, it zooms continuously. How can I implement this with ReactJS?
Jquery: mousedown effect (while left click is held down)
I was using this as a template, but onMousedown doesn't get registered according to console.log
<div className="zoomControl" >
<button className="zoomIn" onMouseDown={this.zoomIn}>+</button>
<button className="zoomOut" onClick={this.zoomOut}>-</button>
</div>
zoomIn = () => {
console.log('test');
var self = this;
this.timeout = setInterval(function(){
// Do something continuously
this.renderer.zoomIn();
}, 100);
return false;
};
zoomMouseUp = () => {
clearInterval(this.timeout);
return false;
};
You need to use both mouseUp and mouseDown. Start a time on mouseDown and call the zoom function with the timeout repeatedly and clear the time on mouseUp.
Here a demo with zoomIn and zoomOut to compare and better understand the algorithm.
Hope this helps!!
class Zoom extends React.Component {
constructor(props) {
super(props)
this.state = {
zoom: 1,
}
this.t = undefined
this.start = 100
this.repeat = this.repeat.bind(this)
this.onMouseDown = this.onMouseDown.bind(this)
this.onMouseUp = this.onMouseUp.bind(this)
this.zoom = this.zoom.bind(this)
this.zoomOut = this.zoomOut.bind(this)
}
zoom(){
this.setState({zoom: this.state.zoom + 0.1})
}
repeat() {
this.zoom()
this.t = setTimeout(this.repeat, this.start)
this.start = this.start / 2
}
onMouseDown() {
this.repeat()
}
onMouseUp() {
clearTimeout(this.t)
this.start = 100
}
zoomOut(){
this.setState({
zoom: 1
})
}
render() {
return <div className="zoomControl" >
<div className="zoom" style={{transform: 'scale('+ this.state.zoom +')'}}></div>
<button className="zoomIn" onMouseUp={this.onMouseUp} onMouseDown={this.onMouseDown}>+</button>
<button className="zoomOut" onClick={this.zoomOut}>-</button>
</div>
}
}
ReactDOM.render(<Zoom/>, document.getElementById('app'))
body {
overflow: hidden
}
.zoom {
width: 20px;
height: 20px;
margin: 0 auto;
background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
If you have to do some kind of animation here, you're better off using requestAnimationFrame than setting intervals. I'd do it something like this.
class App extends React.Component {
state = {
value: 0,
mousedown: false
}
zoom = () => {
if (this.state.mousedown) {
this.setState({ value: this.state.value + 1},
() => { window.requestAnimationFrame(this.zoom) }
)
}
}
zoomIn = () => {
window.requestAnimationFrame(this.zoom);
}
toggleMouseDown = () => {
this.setState({
mousedown: !this.state.mousedown
});
this.zoomIn()
}
render() {
return(
<div>
<button
onMouseUp={this.toggleMouseDown}
onMouseDown={this.toggleMouseDown}>
Click me
</button>
{/* The rest of your component goes here */}
</div>
);
}
}
It's hard to get all of the context, but I'll try to give a relevant answer:
You don't have any property set to call zoomMouseUp when you release the button. I'd start with:
<button className="zoomIn" onMouseDown={this.zoomIn} onMouseUp={this.zoomMouseUp} onMouseOut={this.zoomMouseUp}>+</button>
You stated that it starts zooming, but doesn't stop. That makes me assume it's working, so that should probably fix it. I added the onMouseOut because if they press the button and move the mouse away without releasing it, it's going to continue.
There are a lot of ways to do this, but that's probably the most simple with what you have.
My issue was due to right click being the primary click or some thing along the lines. It works fine as is.

Categories

Resources