Hello! I triyng to complete the functionality displayed on pinned image. I have done this with FlatList's onScroll event, but it change value to slow, active item not always at the middle. Is this some way to always give styles to item, which is on center? Below is my scroll handler.
const onScroll = (event) => {
const { y } = event.nativeEvent.contentOffset;
const { height: layoutHeight } = event.nativeEvent.layoutMeasurement;
const value = Math.floor(y / itemHeight) + Math.floor(layoutHeight / itemHeight / 2);
if (activeItem !== value) {
setActiveItem(value);
}
}
Try to add async/await before setting the active item value
const onScroll = async (event) => {
await const { y } = event.nativeEvent.contentOffset;
await const { height: layoutHeight } = event.nativeEvent.layoutMeasurement;
await const value = Math.floor(y / itemHeight) + Math.floor(layoutHeight / itemHeight / 2);
await if (activeItem !== value) {
setActiveItem(value);
}
}
Related
I'm still beginner with CSS and Javascript. I tried to make a carousel using CSS and JavaScript.
I would like to know how do I create the logic for the dots on my custom carousel?
I created the buttons, and they are working to pass the slides. But can you tell me how do I create the dots?
This is my project into codesandbox
export function usePosition(ref) {
const [prevElement, setPrevElement] = React.useState(null);
const [nextElement, setNextElement] = React.useState(null);
React.useEffect(() => {
const element = ref.current;
const update = () => {
const rect = element.getBoundingClientRect();
const visibleElements = Array.from(element.children).filter((child) => {
const childRect = child.getBoundingClientRect();
return rect.left <= childRect.left && rect.right >= childRect.right;
});
if (visibleElements.length > 0) {
setPrevElement(getPrevElement(visibleElements));
setNextElement(getNextElement(visibleElements));
}
};
update();
element.addEventListener("scroll", update, { passive: true });
return () => {
element.removeEventListener("scroll", update, { passive: true });
};
}, [ref]);
const scrollToElement = React.useCallback(
(element) => {
const currentNode = ref.current;
if (!currentNode || !element) return;
let newScrollPosition;
newScrollPosition =
element.offsetLeft +
element.getBoundingClientRect().width / 2 -
currentNode.getBoundingClientRect().width / 2;
console.log("newScrollPosition: ", newScrollPosition);
currentNode.scroll({
left: newScrollPosition,
behavior: "smooth"
});
},
[ref]
);
const scrollRight = React.useCallback(() => scrollToElement(nextElement), [
scrollToElement,
nextElement
]);
return {
hasItemsOnLeft: prevElement !== null,
hasItemsOnRight: nextElement !== null,
scrollRight,
scrollLeft
};
}
Thank you in advance for any help!!
below you will find my solution, I took your code from the sandbox and worked with it. I didn't understand if you wanted to show the dots, to scroll and sync the dots and click on the dots to change the image, so I did all of them .
codesandbox
This question already has answers here:
Rerender view on browser resize with React
(25 answers)
Closed 1 year ago.
I use useState to get the window height but it gives a runtime error saying that window is not defined. Do you know why
Here's the code:
let [winHeight,setWinHeight] = useState(window.innerHeight)
useEffect(() => {
const list = []
for (var i=0;i<datas.length;i++){
const t = datas[i].title
const res = handleResize(i + 2)
list.push(<li ref={resl[i + 1]} style={{top: "20px",left: res + "px"}}><Anchor href={datas[i].link || `/${t.replace(/ /g, "_")}`}><a onClick={() => closeNav()} onMouseOver={() => setHovering(i)}>{t}</a></Anchor></li>)
}
setWinHeight(window.innerHeight)
setLinks(list)
}, [winHeight])
Have you tried adding window.height in useEffect's dependency array?
useEffect(() => {
const list = []
for (var i=0;i<datas.length;i++){
const t = datas[i].title
const res = handleResize(i + 2)
list.push(<li ref={resl[i + 1]} style={{top: "20px",left: res + "px"}}><Anchor href={datas[i].link || `/${t.replace(/ /g, "_")}`}><a onClick={() => closeNav()} onMouseOver={() => setHovering(i)}>{t}</a></Anchor></li>)
}
setLinks(list)
}, [window.innerHeight])
Try this:
let [winHeight,setWinHeight] = useState(0)
useEffect(()=> {
setWinHeight(window.innerHeight)
},[])
And also you need to import useEffect from 'react'.
window isn't defined because the component isn't renderer at the point you are trying to access window object.
use [window.innerHeight] not [winHeight] inside dependency Array of UseEffect
Are you running on RN? Then you can try this to get the height~
import { Dimensions } from 'react-native';
const windowHeight = Dimensions.get('window').height;
Last Post
Add the resize event listener
Following this one, it would update the winHeight when you change height of window
const lastHeight = useRef(window.innerHeight);
const [winHeight, setWinHeight] = useState(lastHeight.current);
useEffect(() => {
window.addEventListener('resize', (e) => {
const curHeight = e.currentTarget?.innerHeight;
if (lastHeight.current !== curHeight) {
lastHeight.current = curHeight;
setWinHeight(curHeight);
}
});
}, []);
I'm trying to have my Carousel height resize dynamically upon change. However I can't seem trigger a state change purely from a childs height change.
Listening to children was pretty good, infact I'm not fully sure why it's not working.
The problem occurs when an error message is appended to a child within the carousel. It doesn't update.
Currently the best thing I know of to do is an interval...
Is there a better way?
import React, {useState, useEffect, useRef} from 'react';
import './Carousel.scss';
// Wrapped children components must be placed inside of <div> elements
const Carousel = ({slideTo, children}) => {
const [carouselStyle, setCarouselStyle] = useState({});
const activeRef = useRef(null);
const index = slideTo - 1
const newChildren = [];
children.map((d,i) => {
let addClass = (d.props.className !== undefined) ? d.props.className: ""
const newProps = {
key: i
};
if(i === index){
addClass += " active"
newProps.ref = activeRef;
}
newProps.className = addClass.trim();
const newChild = React.cloneElement(d, newProps);
newChildren.push(newChild);
return false
});
const carouselContainerStyle = {
left: (((slideTo * 100) - 100) * -1) + "%",
width: (newChildren.length * 100)+ "%"
}
useEffect(() => {
const interval = setInterval(function(){
console.log("int");
if(activeRef != null){
if(carouselStyle.height === undefined || carouselStyle.height !== activeRef.current.clientHeight){
setCarouselStyle({
height: activeRef.current.clientHeight,
});
}
}
},50)
return () => {
clearInterval(interval)
}
},[]);
useEffect(() => {
console.log("children update");
if(activeRef.current !== null){
setCarouselStyle({
height: activeRef.current.clientHeight,
});
}
},[slideTo,children]);
return (
<div className="carousel" style={carouselStyle}>
<div style={carouselContainerStyle} className="carousel-container">
{newChildren}
</div>
</div>
);
};
export default Carousel;
Implementation
<Carousel slideTo={slide}>
<div><SignIn/></div>
<div><SignUp/></div>
</Carousel>
I am making a javascript variable that's supposed to store an Array (not Nodelist) via the spread syntax:
const $corners = [...document.getElementsByClassName('corner')];
and filtering out all items that don't say 'Empty':
const emptyCorners = $corners.filter(corner => corner.innerText === 'Empty');
I'm getting an obnoxious console error that says:
And of course I'm putting this through babel with the airbnb preset. What am I doing wrong? Thank you for your time. My full code
window.addEventListener('load', () => {
// Determine whether you are going first
const humanTurnFirst = Math.random() >= 0.5;
/**
* Get an array of the text content of each of the tic-tac-toe buttons
* #returns {Array} Array of the text content of each square, from top-left to bottom-right.
*/
const getLayout = () => {
// Array of buttons ordered from top-left to bottom right
const buttons = [
document.getElementsByClassName('corner-top-left')[0],
document.getElementsByClassName('edge-top')[0],
document.getElementsByClassName('corner-top-right')[0],
document.getElementsByClassName('edge-left')[0],
document.getElementsByClassName('center-button')[0],
document.getElementsByClassName('edge-right')[0],
document.getElementsByClassName('corner-bottom-left')[0],
document.getElementsByClassName('edge-bottom')[0],
document.getElementsByClassName('corner-bottom-right')[0],
];
const layout = [];
buttons.forEach(button => layout.push(button.innerText));
return layout;
};
/**
* Make the computer play a square
* #param {Node} button The square to play
*/
const autoClick = (button) => {
const $turn = document.getElementsByClassName('turn')[0];
$turn.innerText = 'Not your turn yet...';
const $allButtons = [...document.getElementsByClassName('button')];
const $allDisableableButtons = $allButtons
.filter(
element => element !== button
&& !element.disabled,
);
$allDisableableButtons.forEach((disableableButton) => {
const thisButton = disableableButton;
thisButton.disabled = true;
});
button.focus();
setTimeout(() => {
button.click();
$allDisableableButtons.forEach((disableableButton) => {
const thisButton = disableableButton;
thisButton.disabled = false;
$turn.innerText = 'Try clicking an empty space.';
});
}, 500);
};
/**
* Calculate the best square for the computer to play.
* #param {Array.<Node>} layout Array of the text of each square, from top-left to bottom right.
* #param {Node|Boolean} previous The last move that you've made.
*/
const computerTurn = (layout, previous, localHumanTurnFirst) => {
const buttons = [
document.getElementsByClassName('corner-top-left')[0],
document.getElementsByClassName('edge-top')[0],
document.getElementsByClassName('corner-top-right')[0],
document.getElementsByClassName('edge-left')[0],
document.getElementsByClassName('center-button')[0],
document.getElementsByClassName('edge-right')[0],
document.getElementsByClassName('corner-bottom-left')[0],
document.getElementsByClassName('edge-bottom')[0],
document.getElementsByClassName('corner-bottom-right')[0],
];
const $corners = [...document.getElementsByClassName('corner')];
// If there is no previous move, the computer goes first with a random corner.
if (!previous) {
const randomBelow4 = Math.floor(Math.random() * 4);
const randomCorner = $corners[randomBelow4];
autoClick(randomCorner);
/* If the computer is going first,
has already filled out a random corner,
and there is nothing in the center,
it will place another X in one of the adgacent corners.
*/
} else if (!localHumanTurnFirst && layout.filter(element => element === 'X').length === 1 && previous !== buttons[4]) {
const filledOutCorner = buttons.filter(element => element.innerText === 'X')[0];
const diagonalCorner = document.getElementsByClassName(filledOutCorner.className
.split(/\s+/)[2]
.replace(/(left|right)/, match => (match === 'left' ? 'right' : 'left'))
.replace(/(top|bottom)/, match => (match === 'top' ? 'bottom' : 'top')))[0];
const emptyCorners = $corners.filter(corner => corner.innerText === 'Empty');
const adjacentCorners = emptyCorners.filter(element => element !== diagonalCorner);
const potentialCorners = adjacentCorners
.filter(
corner => !document.getElementsByClassName(`${corner.className.split(/\s+/)[2].split('-')[1]}-edge`)[0].innerText
&& !document.getElementsByClassName(`${corner.className.split(/\s+/)[2].split('-')[2]}-edge`)[0].innerText,
);
console.log(potentialCorners);
/* const randomPotentialCorner = adjacentCorners[Math.floor(Math.random())];
autoClick(randomPotentialCorner);
*/ }
};
/**
* Add event listener for squares
* #param {Boolean} localHumanTurnFirst Whether you go first.
*/
const squaresOnClick = (localHumanTurnFirst, isHumanTurn) => {
const humanLetter = localHumanTurnFirst ? 'X' : 'O';
const computerLetter = localHumanTurnFirst ? 'O' : 'X';
const $squares = [...document.getElementsByClassName('button')];
$squares.forEach((square) => {
const thisSquare = square;
square.addEventListener('click', () => {
if (isHumanTurn) {
thisSquare.innerText = humanLetter;
computerTurn(getLayout(), thisSquare, localHumanTurnFirst);
squaresOnClick(localHumanTurnFirst, false);
} else {
thisSquare.innerText = computerLetter;
squaresOnClick(localHumanTurnFirst, true);
}
thisSquare.disabled = true;
});
});
};
/**
* Turn the welcome screen into the game screen.
* #param {Boolean} localHumanTurnFirst Whether you go first.
*/
const spawnSquares = (localHumanTurnFirst) => {
const $turn = document.getElementsByClassName('turn')[0];
const $mainGame = document.getElementsByClassName('main-game')[0];
$turn.innerText = 'Try clicking an empty space.';
$mainGame.className = 'main-game dp-4 tic-tac-toe';
$mainGame.setAttribute('aria-label', 'Tic-tac-toe grid');
$mainGame.innerHTML = `
<button class="button corner corner-top-left corner-top corner-left">Empty</button>
<button class="button edge edge-top">Empty</button>
<button class="button corner corner-top-right corner-top corner-right">Empty</button>
<button class="button edge edge-left">Empty</button>
<button class="button center-button">Empty</button>
<button class="button edge edge-right">Empty</button>
<button class="button corner corner-bottom-left corner-bottom corner-left">Empty</button>
<button class="button edge edge-bottom">Empty</button>
<button class="button corner corner-bottom-right corner-bottom corner-right">Empty</button>
`;
squaresOnClick(localHumanTurnFirst, localHumanTurnFirst);
if (!localHumanTurnFirst) {
computerTurn(getLayout(), false, localHumanTurnFirst);
}
};
/**
* Create the button that starts the game.
*/
const welcomeButton = (localHumanTurnFirst) => {
const $welcomeButton = document.getElementsByClassName('start-button')[0];
$welcomeButton.addEventListener('click', () => spawnSquares(localHumanTurnFirst));
};
/**
* Turn the main game into the welcome screen.
* #param {Boolean} localHumanTurnFirst Whether you go first.
*/
const welcome = (localHumanTurnFirst) => {
const $mainGame = document.getElementsByClassName('main-game')[0];
const $turn = document.getElementsByClassName('turn')[0];
$turn.innerText = 'Welcome!';
$mainGame.className = 'main-game dp-4 welcome center';
$mainGame.innerHTML = `
<section class="welcome-section">
<h2 class="welcome-heading">Welcome to unbeatable tic-tac-toe!</h2>
<p class="welcome-text">
According to random chance, your turn has already been chosen
as ${localHumanTurnFirst ? 'first (with an X)' : 'second (with an O)'}, which
means that the computer is going
${localHumanTurnFirst ? 'second (with an O)' : 'first (with an X)'}. <strong>
Press the start button to start the game!</strong
>
</p>
</section>
<button class="start-button button">Start</button>
`;
welcomeButton(localHumanTurnFirst);
};
welcome(humanTurnFirst);
});
EDIT:
The console output for console.logging corners in the filter callback is:
Whereas just document.getElementsByClassName('corner'):
So, it must be something to do with the change in format that's causing the problem.
So the error lies a few lines below, here:
const potentialCorners = adjacentCorners
.filter(
corner => !document.getElementsByClassName(`${corner.className.split(/\s+/)[2].split('-')[1]}-edge`)[0].innerText
&& !document.getElementsByClassName(`${corner.className.split(/\s+/)[2].split('-')[2]}-edge`)[0].innerText,
);
You have classes like this: edge-* and not like this: *-edge so you have to change the selector here:
const potentialCorners = adjacentCorners
.filter(
corner => !document.getElementsByClassName(`edge-${corner.className.split(/\s+/)[2].split('-')[1]}`)[0].innerText
&& !document.getElementsByClassName(`edge-${corner.className.split(/\s+/)[2].split('-')[2]}`)[0].innerText,
);
Also, the weird format you see in the Chrome console is just the representation of the HTML Elements in the console. It just mean that those are HTML Elements and Chrome simply displays them like that
I'm experimenting with drag-and-drop using cyclejs in a codepen. The standard drag methods supported by HTML 5 don't seem to support constraints on the movement of the dragged object so I went with standard mousedown/mousemove/mouseup. It works, but not consistently. The combine() operation doesn't seem to trigger even when the debug() calls show that mousedown and mousemove events have been received and sometimes the mouseup is missed. Perhaps my understanding of the operation is incomplete or incorrect. A direct link to the codepen is provided at the bottom of this post. Any help appreciated!
const xs = xstream.default;
const { run } = Cycle;
const { div, svg, makeDOMDriver } = CycleDOM;
function DragBox(sources) {
const COMPONENT_NAME = `DragBox`;
const intent = function({ DOM }) {
return {
mousedown$: DOM.select(`#${COMPONENT_NAME}`)
.events("mousedown")
.map(function(ev) {
return ev;
})
.debug("mousedown"),
mousemove$: DOM.select(`#${COMPONENT_NAME}`)
.events("mousemove")
.map(function(ev) {
return ev;
})
.debug("mousemove"),
mouseup$: DOM.select("#container")
.events("mouseup")
.map(function(ev) {
return ev;
})
.debug("mouseup")
};
};
const between = (first, second) => {
return source => first.mapTo(source.endWhen(second)).flatten();
};
const model = function({ mousedown$, mousemove$, mouseup$ }) {
return xs
.combine(mousedown$, mousemove$)
.debug("combine")
.map(([mousedown, mousemove]) => ({
x: mousemove.pageX - mousedown.layerX,
y: mousemove.pageY - mousedown.layerY
}))
.compose(between(mousedown$, mouseup$))
.startWith({
x: 0,
y: 0
})
.debug("model");
};
const getStyle = left => top => {
return {
style: {
position: "absolute",
left: left + "px",
top: top + "px",
backgroundColor: "#333",
cursor: "move"
}
};
};
const view = function(state$) {
return state$.map(value =>
div("#container", { style: { height: "100vh" } }, [
div(`#${COMPONENT_NAME}`, getStyle(value.x)(value.y), "Move Me!")
])
);
};
const actions = intent(sources);
const state$ = model(actions);
const vTree$ = view(state$);
return {
DOM: vTree$
};
}
function main(sources) {
const dragBox = DragBox(sources);
const sinks = {
DOM: dragBox.DOM
};
return sinks;
}
Cycle.run(main, {
DOM: makeDOMDriver("#app")
});
https://codepen.io/velociflapter/pen/bvqMGp?editors=1111
Testing your code shows that combine is not getting the first mousedown event, apparently due to the between operator subscribing to mousedown$ after the first mousedown event. Adding remember to the mousedown$ sends that first mousedown event to the between operator subscription.
mousedown$: DOM.select(`#${COMPONENT_NAME}`)
.events("mousedown").remember()
Codepen.io remember example.
Codesandbox.io testing between
Here's another CycleJS/xstream Drag and Drop approach (taking inspiration from this RxJS Drag and Drop example) I think is more straight forward. Everything else in your code is essentially the same but the model function is this:
const model = function({ mousedown$, mousemove$, mouseup$ }) {
return mousedown$.map(md => {
let startX = md.offsetX, startY = md.offsetY
return mousemove$.map(mm => {
mm.preventDefault()
return {
x: mm.clientX - startX,
y: mm.clientY - startY
}
}).endWhen(mouseup$)
}).flatten().startWith({x:0,y:0})
};
Here's a Codepen.io example.