Bug found in scrolling smooth code found in someone else post but don't know how to fix - smooth-scrolling

I search on my friend Google for some code to do smooth scroll and found this : Smooth vertical scrolling on mouse wheel in vanilla javascript?
It works well but if i scroll once and then try to use my mouse to manually move the scrollbar, it's broken...
SmoothScroll(document, 120, 12);
function SmoothScroll(target, speed, smooth) {
if (target === document)
target = (document.scrollingElement ||
document.documentElement ||
document.body.parentNode ||
document.body) // cross browser support for document scrolling
var moving = false
var pos = target.scrollTop
var frame = target === document.body &&
document.documentElement ?
document.documentElement :
target // safari is the new IE
target.addEventListener('scroll', scrolled, {
passive: false
})
target.addEventListener('mousewheel', scrolled, {
passive: false
})
target.addEventListener('DOMMouseScroll', scrolled, {
passive: false
})
function scrolled(e) {
e.preventDefault(); // disable default scrolling
var delta = normalizeWheelDelta(e)
pos += -delta * speed
pos = Math.max(0, Math.min(pos, target.scrollHeight - frame.clientHeight)) // limit scrolling
if (!moving) update()
}
function normalizeWheelDelta(e) {
if (e.detail) {
if (e.wheelDelta)
return e.wheelDelta / e.detail / 40 * (e.detail > 0 ? 1 : -1) // Opera
else
return -e.detail / 3 // Firefox
} else
return e.wheelDelta / 120 // IE,Safari,Chrome
}
function update() {
moving = true
var delta = (pos - target.scrollTop) / smooth
target.scrollTop += delta
if (Math.abs(delta) > 0.5)
requestFrame(update)
else
moving = false
}
var requestFrame = function () { // requestAnimationFrame cross browser
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (func) {
window.setTimeout(func, 1000 / 50);
}
);
}()
}
So... i want it to work properly when i already scroll once but try to use the mouse to move the scrollbar instead of mousewheel.
Thanks for helping!

Looks like you could fix it by re-adjusting the pos variable to the scrollTop before your scrolling calculations.
Additionally theres a bug where your scroll could get stuck in an infinite render loop causing you to never stop animating. This was due to the delta being .5 < delta < 1 making the request frame get called forever. You cant actually move the scrollTop anything less than 1 so I adjusted the conditions for another render loop and rounded the delta
function scrolled(e) {
// if currently not animating make sure our pos is up to date with the current scroll postion
if(!moving) {
pos = target.scrollTop;
}
e.preventDefault(); // disable default scrolling
var delta = normalizeWheelDelta(e)
pos += -delta * speed
pos = Math.max(0, Math.min(pos, target.scrollHeight - frame.clientHeight)) // limit scrolling
if (!moving) update()
}
function update() {
moving = true;
// scrollTop is an integer and moving it by anything less than a whole number wont do anything
// to prevent a noop and an infinite loop we need to round it
var delta = absRound((pos - target.scrollTop) / smooth)
target.scrollTop += delta
if (Math.abs(delta) >= 1) {
requestFrame(update)
} else {
moving = false
}
}
function absRound(num) {
if(num < 0) {
return -1*Math.round(-1*num);
} else {
return Math.round(num);
}
}
That way when manually adjusting the scroll position if the wheel is used it doesnt jump to the position it was once at, but instead adjust itself to the current scroll position.

Related

D3 Zoom - Scales

I am new to D3 and trying to use zoom functionality.
I have a 100 thousand plus points to be displayed and inorder to not do break my browser, I am trying to randomly select 5000 points and show the user, and on zooming in - query more relevant points from the database.
I do not want to do this on every zoom , my zoom scaleExtent is set to [1,5] and every time the zoomscale is 3 or 5 I will query data from database and refresh the plot, this works fine, just a problem every time there is a plot refresh the scales are set to [1,5] again and it goes in an endless loop. And zoom out completely breaks.
Anybody have any ideas how to approach doing this.
I tried to create to buttons for zoomin and zoomout and call the function as below onClick them
function initiateZoom(val)
{
var direction = 1
direction = (val === 'zoom_in') ? 1 : -1;
extent = zoom.scaleExtent();
incrZoom = parseInt(lastZoomScale) + parseInt(direction);
console.log("Target Zoom Scale"+ incrZoom);
if (incrZoom < extent[0] || incrZoom > extent[1])
{
if(incrZoom < extent[0])
{
incrZoom = parseInt(incrZoom) + direction;
}else {
incrZoom = parseInt(incrZoom) + direction;
}
alert("Min/Max Reached");
return false;
}
zoom.scaleTo(scatter_plt_svg, (incrZoom));
}
And on zoom call the function zoomed
function zoomed() {
var zoom_scale = (d3.event.transform.k);//.toFixed(2);
var new_xScale= null;
var new_yScale = null;
new_xScale = d3.event.transform.rescaleX(xScl);
new_yScale = d3.event.transform.rescaleY(yScl);
// update axes
gX.call(d3.axisBottom(new_xScale).ticks(10));
gY.call(d3.axisLeft(new_yScale).ticks(10));
circles.data(plotdata)
.attr('cx', function (d, i) {
return new_xScale(d[1])
})
.attr('cy', function (d, i) {
return new_yScale(d[2])
});
curr_xScale = new_xScale;
curr_yScale = new_yScale;
lastZoomScale = zoom_scale;
//If zoom scale is a value of scaleExtent, refresh plot.
if(zoom_scale == 2 || zoom_scale == 4 || zoom_scale == 6 || zoom_scale == 8 || zoom_scale == 10)
{
scatter_plt_svg.on('.zoom', null);
initialize_plots(false,"zoom"); //helps get data from server and refresh plots.
}
}
Regards
Tasneem

Triggering a Lottie animation onScroll

im currently building a website using fullpage js and lottie animations. Now im trying to trigger an animation when the user scrolls to the section with the animation. Here is what i tried:
(please note that im very new to js)
$(document).ready(function($) {'use strict';
$('#fullpage').fullpage({
sectionsColor: ['white', '#004E8A', 'white','#004E8A', 'white', '#004E8A',
'white','#004E8A', 'white'],
anchors:['startseite','pers_vermittler','team','konzept','rechner','mod_portfolio','sicherheit','absatz'],
onLeave: function(index, nextIndex, direction) {
if( index == 3 && direction == 'down' ) {
lottie.play('k2an');
}
(at the end of the body section ->)
<script>
var params = {
container: document.getElementById('k2an'),
renderer: 'svg',
loop: true,
autoplay: false,
path: 'k2an.json',
};
anim = lottie.loadAnimation(params);
You should be using fullPage.js callbacks to fire your JS animations.
See the example:
$('#fullpage').fullpage({
anchors: ['firstPage', 'secondPage', 'thirdPage', 'fourthPage', 'lastPage'],
afterLoad: function(anchorLink, index){
var loadedSection = $(this);
//using index
if(index == 3){
alert("Section 3 ended loading");
}
//using anchorLink
if(anchorLink == 'secondSlide'){
alert("Section 2 ended loading");
}
}
});
Feel free to also check my video tutorial on how to create animations using fullPage.js state classes.
Right now im using this approach on a couple production sites. It plays the animation as the user scrolls.
I basically check how much of the animation objects box is visible in the viewport, calculate the total length of the animation (in frames) and then project the percentage to a frame where i gotoAndStop().
var anim = <YOUR LOTTIE ANIMATION OBJECT>
// play around with these
var speed = 1; // speed of animation
var scrollOffset = 0; // start animation sooner / later
function scrollHandler() {
if (!anim.isLoaded) return;
p = percentageSeen(e) / 100 - (scrollOffset || 0);
if (p <= 0 || p >= 1) return
var length = anim.totalFrames / anim.frameModifier;
var pos = length * p * (speed || 1);
anim.goToAndStop(pos);
}
$(window).on('scroll', scrollHandler);
/**
* returns percentage of scrolling element through viewport
* 0% until element-middle is at bottom of viewport
* 100% if element-middle is at top of viewport
*
* #param id
* #returns {number}
*/
function percentageSeen(idOrElement) {
var $element;
if (typeof idOrElement === 'object') {
$element = idOrElement;
} else {
$element = $('#' + id);
if (!$element[0]) return 0;
}
var $win = $(window), viewportHeight = $(window).height(),
scrollTop = $win.scrollTop(),
elementOffsetTop = $element.offset().top,
elementHeight = $element.height();
if (elementOffsetTop > (scrollTop + viewportHeight)) {
return 0;
} else if ((elementOffsetTop + elementHeight) < scrollTop) {
return 100;
} else {
var distance = (scrollTop + viewportHeight) - elementOffsetTop - (elementHeight / 2);
if (distance < 0) return 0;
var percentage = distance / (viewportHeight / 100);
if (percentage > 100) return 100;
return percentage;
}
}
If you want to only start the animation and let it run (independently of further user-scrolling-behaviour), just use the jquery inview plugin, disable autoplay on your animation and trigger the play() once like this:
$(".animation-container").one("inview", function() {
anim.play()
});

I want change scrollview rolling speed in react native

Now I use interval make it come true, but it is very incoherence.
If I can just change the method (scroll) speed, it well be nice.
this.interval = setInterval(()=>{
if(!_scroll){
_this.interval && clearInterval(_this.interval);
}
if(totalWide+ScreenWidth >= width ){
_scroll.scrollWithoutAnimationTo();
totalWide=0;
i=0;
}else{
_scroll.scrollTo({x:eachWide*i,animate:true});
totalWide = totalWide + eachWide;
i= i+1;
}
},250)
use decelerationRate property of ScrollView
<ScrollView decelerationRate={0.5}>
</ScrollView>
I got this working by having setInterval call a function(in which you define the logic or the pace at which the scroll should move).
this.interval= setInterval(this.scrollwithSpeed, 100); // Set the function to this timer
scrollwithSpeed() {
position = this.state.currentPosition + x; // x decides the speed and
currentPosition is set to 0 initially.
this.scrollObject.scrollTo(
{ y: position, animated: true }
);
this.setState({ currentPosition: position });
}
Make sure you call clearInterval(this.interval) after it is done.
I would suggest to attach to js requestAnimationFrame (from how far I know it is supported in React Native).
Bellow example will scroll linearly from top to bottom. If You need to scoll to different offset just change distance variable.
startingPoint variable is redundant in scrolling from top to bottom but will stay in example.
scroll() {
if (this.scrollAnimationFrame) {
cancelAnimationFrame(this.scrollAnimationFrame);
}
this.listRef.scrollToOffset({offset: 0, animated: false}); // remove if You don't start scroll from top
const duration = this.scrollTime,
startingPoint = 0, // change if You don't start scroll from top
distance = Scrolling.LINE_HEIGHT * Scrolling.ITEMS_COUNT;
let startTimestamp, progress;
const frameCallback = (timestamp) => {
if (!startTimestamp) {
startTimestamp = timestamp;
}
progress = timestamp - startTimestamp;
this.listRef.scrollToOffset({
offset: distance * (progress / duration) + startingPoint,
animated: false,
});
if (progress < duration) {
this.scrollAnimationFrame = requestAnimationFrame(frameCallback);
}
};
this.scrollAnimationFrame = requestAnimationFrame(frameCallback);
}
You can use reanimated to make it work.
const offsetY = useSharedValue(0);
const animatedProps = useAnimatedProps<FlatListProps<unknown>>(() => {
return {
contentOffset: {
x: 0,
y: offsetY.value,
},
};
});
const handleScroll = () => {
offsetY.value = withTiming(targetIndex * CARD_HEIGHT, {
duration: YOUR_DURATION_HERE,
});
}
return <Animated.FlatList animatedProps={animatedProps} ... />

hammer.js detect variables in panmove and unbind when it hits certain criteria

my goal is detect when an element has reached a certain margin-left, and than unbind or stop the panmove from continuing if it hits that threshold.
I have a "panmove" bound to an element using hammer.js, and jquery hammer plugin.
I noticed that in the panmove, console.log(e) will fire hundreds of times as you move the elements, which is expected. If you however put an if statement in the panmove function, it only goes off of the initial state of the first panmove and not the current one.
.bind("panmove", function (e) {
var count = 0;
console.log(e);
console.log(count++);
var _this = $(e.target);
var _thisDataLeft = _this.attr("data-left");
var _thisDataMaxLeft = _this.attr("data-maxleft"); // this is derived from the width of the Delete box, which can be any width.
if (Math.abs(_thisDataLeft) < Number(_thisDataMaxLeft)) {
_this.css({ left: Number(_thisDataLeft) + e.gesture.deltaX }); // controls movement of top layer
console.log(count++);
}
I noticed that the console.log(count++) always fires 1, instead of iterating up, as if it is only reading it once in the beginning.
How can I run an if statement inside of this Pan, so that it is always the current information, and not just the first iteration?
Ended up moving away from Hammer.js, was not able to get the results I needed. It looks like the more basic jquery.event.move.js was easier to use than hammer.
here is my example in js fiddle
https://jsfiddle.net/williamhowley/o9uvo50y/
$(document).ready(function () {
// http://stephband.info/jquery.event.move/
// http://stephband.info/jquery.event.swipe/
// add swipe functionality to the rows.
// I think you will need to add the swipe left, after it is activated by a HOLD down press.
// idk, how do you always make something swipable.
var wrap = $('ul#main');
$('ul#main > li')
.on('movestart', function (e) {
console.log("move start");
// var $li = $(e.target).closest('.swipable'); // this would be normal live integration
var $li = $(e.target);
if ($li.attr("data-hasplaceholder") !== "true") { // if it does not have a placeholder, add one.
createBackgroundSpacer($li);
$li.attr("data-hasplaceholder", true); // signify that a placeholder has been created for this element already.
}
// If the movestart heads off in a upwards or downwards
// direction, prevent it so that the browser scrolls normally.
if ((e.distX > e.distY && e.distX < -e.distY) ||
(e.distX < e.distY && e.distX > -e.distY)) {
e.preventDefault();
return;
}
// To allow the slide to keep step with the finger,
// temporarily disable transitions.
wrap.addClass('notransition'); // add this to the container wrapper.
})
.on('move', function (e) {
// event definitions
// startX : 184, where from left the mouse curser started.
// deltaX: ?
// distX: how far the mouse has moved, if negative moving left. Still need to account for double movement, currently can only handle one movement.
console.log("move");
console.log(e);
var maxLeft = $('.rightContent').width();
var marginLeftNum = Number($(this).css('margin-left').replace(/[^-\d\.]/g, ''));
if (marginLeftNum <= -maxLeft && e.deltaX < 0) { // Case when user is at outermost left threshold, and trying to move farther left.
$(this).css({ 'margin-left': -maxLeft });
}
else if (marginLeftNum == -maxLeft && e.deltaX > 0) { // When user is at threshold, and trying to move back right.
$(this).css({ 'margin-left': marginLeftNum + e.deltaX });
}
else if (e.target.offsetLeft>=0 && e.deltaX>0) { // If the offset is 0 or more, and the user is scrolling right (which is a positive delta, than limit the element. )
$(this).css({ 'margin-left': 0 });
}
// Must have a Negative offset, and e.deltaX is Negative so it is moving left.
else if (e.deltaX < 0) { // Case when element is at 0, and mouse movement is going left.
$(this).css({ 'margin-left': marginLeftNum + e.deltaX });
}
else { // Moving Right when not on 0
$(this).css({ 'margin-left': marginLeftNum + e.deltaX });
}
})
.on('swipeleft', function (e) {
console.log("swipeleft");
})
.on('activate', function (e) {
// not seeing this activate go off, i think this is custom function we can add on if swipe left hits a threshold or something.
console.log("activate");
})
.on('moveend', function (e) {
console.log("move end");
wrap.removeClass('notransition');
});
var createBackgroundSpacer = function ($shoppingListRow) {
var border = 2;
$shoppingListRow.css({ 'width': $shoppingListRow.width() + border, 'height': $shoppingListRow.height() + border }); // gives itself set width and height
$shoppingListRow.addClass('swipable');
// placeholder HTML
var leftPlaceholder = $('<div class="leftPlaceholder"></div>').css({ 'height': $shoppingListRow.height()});
var rightPlaceholder = $('<div class="rightPlaceholder"></div>')
var rightContent = $('<div class="rightContent">Delete</div>').css({ 'height': $shoppingListRow.height()});
rightPlaceholder.append(rightContent);
var placeHolder = $('<div class="swipePlaceholder clearfix"></div>'); // goes around the two floats.
placeHolder.css({ 'width': $shoppingListRow.width(), 'height': $shoppingListRow.height() });
placeHolder.append(leftPlaceholder, rightPlaceholder);
$shoppingListRow.before(placeHolder); // adds placeholder before the row.
$shoppingListRow.css({ 'marginTop': -($shoppingListRow.height() + border) });
};
});

Play an animation when touch moved is certain distance from touch began

i am new to unityscript and unity and i am trying to make an animation trigger when the touch moved position is +100 to the right of touch began, so i have also tried +500 and +1000 and it seems that the animation is playing when the touch is past 100,500,or 1000 on the screen, not the touch.began position + (the amount), any help is appreciated, thank you for your time as i am new to unityscript
#pragma strict
var distance : float = 10;
var joystick : GameObject;
private var first : boolean = false;
function Start () {
}
function Update () {
transform.eulerAngles = Vector3(0,Camera.main.transform.eulerAngles.y + 180,0);
var v3Pos : Vector3;
if (Input.touchCount > 0 &&
Input.GetTouch(0).phase == TouchPhase.Began) {
// Get movement of the finger since last frame
var touchDeltaPosition:Vector2 = Input.GetTouch(0).position;
if(!first){
var touchdet : Vector2 = touchDeltaPosition;
first = true;
}
// Move object across XY plane
v3Pos = Vector3(touchDeltaPosition.x, touchDeltaPosition.y, distance);
transform.position = Camera.main.ScreenToWorldPoint(v3Pos);
}
if (Input.touchCount > 0 &&
Input.GetTouch(0).phase == TouchPhase.Moved) {
// Get movement of the finger since last frame
var touchAlphaPosition:Vector2 = Input.GetTouch(0).position;
// Move object across XY plane
v3Pos = Vector3(touchAlphaPosition.x, touchAlphaPosition.y, distance);
transform.position = Camera.main.ScreenToWorldPoint(v3Pos);
}
if (Input.touchCount > 0 &&
(Input.GetTouch(0).phase == TouchPhase.Ended || Input.GetTouch(0).phase == TouchPhase.Canceled )) {
// Get movement of the finger since last frame
var touchBetaPosition:Vector2 = Input.GetTouch(0).position;
first = false;
// Move object across XY plane
v3Pos = Vector3(touchBetaPosition.x, 600, distance);
transform.position = Camera.main.ScreenToWorldPoint(v3Pos);
}
if(first)
{
if(touchAlphaPosition.x > touchdet.x + 100)
{
animation.Play("Right");
}
}
}
The variable touchDet is declared and initialized in the function Update, so the value is not persisted between function calls. touchDet in all but the iteration where TouchPhase.Began event fires will always be equal to Vector2.zero.

Resources