How can I implement vertical scroll snapping with `IntersectionObserver`? - css

I am inspired by Rolls Royce website and want to implement the same scroll snapping feature in mine as well, I did it with the HTML default scroll-snap-type which gives me expected behavior but creates two scrollbars, one for the container and another one for the body, which is not expected so I tried to go with the IntersectionObserver but it causes an issue, I can travel to only adjacent slide when directly jumping from 1st slide to 3rd slide.
Here is the code sandbox link: https://codesandbox.io/s/scrollsnap-forked-pre0c?file=/pages/index.vue
Here is the code that I am working
<template>
<main class="landing">
<nav class="scroller">
<ul class="scroller__list">
<li
class="scroller__item"
v-for="(slide, index) in slides"
:key="index"
#click.prevent="scroll(slide.id)"
>
<a
class="scroller__dot"
:href="'#' + slide.id"
#click.prevent="scroll(slide.id)"
></a>
</li>
</ul>
</nav>
<div class="slides-container">
<slide
class="slide"
v-for="(slide, index) in slides"
:key="index"
:img="slide.img"
:id="slide.id"
:format="slide.format"
:filter="slide.filter"
>{{ slide.content }}</slide
>
</div>
</main>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
data() {
return {
slides: [
{
img: "car-slide-1.png",
content: "hello world",
id: "car-slide-1",
filter: "color-burn",
},
{
img: "car-slide-2.png",
// promo-video.mp4
content: "Second Car",
id: "car-slide-2",
filter: "color-burn",
// format: "video",
},
{
img: "car-slide-3.png",
content: "Third slide",
id: "car-slide-3",
filter: "color-burn",
},
],
observer: null as any as IntersectionObserver,
options: {
threshold: [0.5],
root: process.browser
? document.getElementsByClassName("slides-container")[0]
: null,
} as IntersectionObserverInit,
};
},
methods: {
scroll(id: string, who: string | null = null) {
console.log("scrolling to ", id, who ? "by " + who : "");
document.getElementById(id)?.scrollIntoView({
behavior: "smooth",
block: "start",
});
},
},
mounted() {
let scrolling = false;
this.observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !scrolling) {
let top = entry.boundingClientRect.top;
scrolling = true;
window.scroll({ behavior: "smooth", top: window.pageYOffset + top });
}
scrolling = false;
});
}, this.options);
document
.querySelectorAll(".slide")
.forEach((slide) => this.observer.observe(slide));
},
});
</script>

Related

Hover on dynamic generated span and give the hovered icon active class in Vue

I want to hover on these Music notes, if i hover on music note 2, then the 1, 2 both should get active classes. And if i hover on 3 then all 3 spans/icons should get active classes.
Like i have done with click event.
So how to do that same thing with hover?
My code looks like this:
<template>
<div class="track-rating">
<span :key="note" v-for="note in maxNotes" :class="{ 'active': note <= notes }" #click="rate(note)" class="material-icons mr-1">
audiotrack
</span>
</div>
</template>
<script>
export default {
name: "Rating",
props: {
rating: {
type: Number,
required: true
},
maxNotes: {
type: Number,
default: 3
},
hasCounter: {
type: Boolean,
default: true
}
},
data() {
return {
notes: this.rating
};
},
methods: {
rate(note) {
if (typeof note === 'number' && note <= this.maxNotes && note >= 0)
this.notes = this.notes === note ? note - 1 : note
}
}
};
</script>
================
<template>
<div>
<Rating :rating="0"/>
</div>
</template>
<script>
import Rating from '../Rating';
export default {
name: "Test",
components: {
Rating
},
};
</script>
Try this, store the currently hovered note and use that to also add the active class.
<template>
<div class="track-rating">
<span :key="note"
v-for="note in maxNotes"
:class="{ 'active': note <= notes || note <= hoveredNote }"
#mouseover="hoveredNote = note"
#mouseleave="hoveredNote = false"
#click="rate(note)" class="material-icons mr-1">
audiotrack
</span>
</div>
</template>
<script>
export default {
name: "Rating",
props: {
rating: {
type: Number,
required: true
},
maxNotes: {
type: Number,
default: 3
},
hasCounter: {
type: Boolean,
default: true
}
},
data() {
return {
notes: this.rating,
hoveredNote: false,
};
},
methods: {
rate(note) {
if (typeof note === 'number' && note <= this.maxNotes && note >= 0)
this.notes = this.notes === note ? note - 1 : note
}
}
};
</script>

How to apply the animation with the for loop in the element?

I am trying to animate this on page load. I tried many ways but eighter the animation happing all at once for all the elements or not happing at all .
I also tried the vue transition-group but the v-on:enter doesn't work with nuxt.
The major issue is how to get it working on load of the page.
This is the link how it should work click this codepen link
<template>
<div class="greybackground" style="height:100vh">
<div class="exprience">
<div class="aboutme">
<span class="firsttext title is-1">Experience</span>
<br />
<br />
<p class="intro textwhite">
I'm the co-founder of Gruntwork, a company that helps startups get up
and running on AWS with DevOps best practices and world-class
infrastructure. Our mission is to make it an order of magnitude easier
to understand, develop, and deploy software. We take care of all the
"undifferentiated heavy lifting"—the grunt work—so that your team can
focus on the products and services unique to your company.
</p>
</div>
<div class="onend">
<div class="wrapperprogbox">
<h3>Language I speak</h3>
<div class="content">
<div class="progbargroup">
<div>
<!-- <transition-group name="list" tag="p"> -->
<span
class="proglist"
v-for="(item, index) in langs"
:key="index"
:item.sync="langs"
>
<div class="slideInLeft">
<div class="progbar">
<span class="textwhite ">{{ item.langname }}</span>
<transition name="slide-left" tag="p">
<progress
class="progress is-small is-primary progbartitle "
:value="item.score"
max="100"
>15%</progress
>
</transition>
</div>
</div>
</span>
<!-- </transition-group> -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
// import progbar from "../components/progbar";
import { FadeTransition } from "vue2-transitions";
import Velocity from "velocity-animate";
export default {
components: {
FadeTransition
},
data() {
return {
// langs: ["Golang", "JavaScript", "TypeScript", "HTML", "CSS"],
show: false,
// displayList:false,
style: { enter: 300, leave: 300 },
langs: [
{ langname: "Golang", score: "63" },
{ langname: "JavaScript", score: "90" },
{ langname: "TypeScript", score: "34" },
{ langname: "HTML", score: "88" },
{ langname: "CSS", score: "65" }
]
};
},
mounted() {
this.init();
this.$nuxt.$on("STAG_ANIMATE", this.AnimateDelayList());
},
methods: {
init() {
this.show = true;
// this.enter();
this.AnimateDelayList();
},
AnimateDelayList() {
// get vendor transition property
this.displayList = true;
var items = document.querySelectorAll(".proglist");
var docElemStyle = document.documentElement.style;
var transitionProp =
typeof docElemStyle.transition == "string"
? "transition"
: "WebkitTransition";
console.log("Style element", docElemStyle, transitionProp, items);
for (var i = 0; i < items.length; i++) {
var item = items[i];
// stagger transition with transitionDelay
item.style[transitionProp + "Delay"] = i * 50 + "ms";
console.log("this is i", i);
// item.classList.toggle("is-moved");
}
},
}
};
</script>

Change height of Slick Carousel

Here is a fiddle of Slick Carousel embedded in a Bootstrap thumbnail.
JSFiddle
How can I make the carousel only 200px tall and ensure that the images are scaled proportionally? I can't seem to get the carousel to fit inside a container who's height I dictate.
NOTE: Resize your browser after loading this fiddle! This works around a known bug where the plugin layout is not initializing on page load. This is not the issue I'm needing solved. Ignore this issue.
HTML
<div ng-app="slickExampleApp" class="background">
<div ng-controller="SlickCtrl">
<div class="inner-container row">
<div class="thumbnail col-lg-6 col-lg-offset-3 col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3 col-xs-10 col-xs-offset-1">
<div ng-repeat="result in results">
<slick-carousel
settings="slickConfig"
media="result.images">
</slick-carousel>
<div class="row">
<div class="caption">
<h4 class="heading">{{result.heading}}</h4>
<p class="body">{{result.body}}</p>
<p class="text-center">
Place Offer
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
JS
var app = angular.module('slickExampleApp', ['slick']);
app.controller('SlickCtrl', function ($scope) {
$scope.slickConfig = {
dots: true,
lazyLoad: 'progressive',
infinite: true,
speed: 100,
slidesToScroll: 1,
//adaptiveHeight: true,
//TODO: Track this bug to allow for variableWidth on next release: https://github.com/kenwheeler/slick/issues/790
variableWidth: true,
onInit: function () {
jQuery(window).resize();
console.log('slickcaroseal locded');
},
centerMode: true
};
$scope.results = [
{
"annotations": {
"latlong_source": "In Posting",
"proxy_ip": "107.191.98.50:22225",
"source_account": "rmk8g-4822965821#sale.craigslist.org",
"source_cat": "sss",
"source_continent": "USA",
"source_heading": "\" Kennedy Machinists 8 Drawer Roller Cabinet, Kennedy Combination Set",
"source_loc": "sfbay",
"source_map_google": "https://maps.google.com/maps/preview/#37.759300,-122.483600,16z",
"source_map_yahoo": "http://maps.yahoo.com/#mvt=m&lat=37.759300&lon=-122.483600&zoom=16",
"source_neighborhood": "inner sunset / UCSF",
"source_state": "California",
"source_subcat": "tla|tls",
"source_subloc": "sfc"
},
"body": "\n \" Kennedy Machinists 8 Drawer Roller Cabinet, and Kennedy Combination and Machinist Chest Set with keys\".\nVery good condition. Asking Whole set for $875 or Best Offer (REASONABLE!!!!!).\nPlease email with your contact phone number if you are interest and SERIOUS buyer. Thanks.\n ",
"category": "STOO",
"category_group": "SSSS",
"external_id": "4822965821",
"external_url": "http://sfbay.craigslist.org/sfc/tls/4822965821.html",
"heading": " Kennedy Machinists 8 Drawer Roller Cabinet, Kennedy Combination Set",
"images": [
{
"full": "http://images.craigslist.org/00707_cwYj2bMonC8_600x450.jpg"
},
{
"full": "http://images.craigslist.org/00w0w_8b36BjRL4YM_600x450.jpg"
},
{
"full": "http://images.craigslist.org/00U0U_6MKF9DWjRfM_600x450.jpg"
},
{
"full": "http://images.craigslist.org/00d0d_4bX1cj3aIrf_600x450.jpg"
},
{
"full": "http://images.craigslist.org/00B0B_8i444xC2DKt_600x450.jpg"
},
{
"full": "http://images.craigslist.org/00F0F_1CnjxJRlvXt_600x450.jpg"
}
],
"location": {
"accuracy": 8,
"city": "USA-SFO-SNF",
"country": "USA",
"county": "USA-CA-SAF",
"geolocation_status": 3,
"lat": "37.7593",
"locality": "USA-SFO-OUS",
"long": "-122.4836",
"metro": "USA-SFO",
"region": "USA-SFO-SAF",
"state": "USA-CA",
"zipcode": "USA-94122"
},
"price": 875,
"source": "CRAIG",
"timestamp": 1419808764
}
];
});
//Custom implementation of https://github.com/kbdaitch/angular-slick-carousel
//Var needed for slick carousel directives below.
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
app.directive('onFinishRender', function() {
return {
restrict: 'A',
link: function(scope, element, attr) {
if (scope.$last === true) {
return scope.$evalAsync(attr.onFinishRender);
}
}
};
});
app.directive('slickCarousel', [
'$timeout', '$templateCache', function($timeout, $templateCache) {
var SLICK_FUNCTION_WHITELIST, SLICK_OPTION_WHITELIST, isEmpty;
$templateCache.put('angular-slick-carousel/template.html', "<div class=\"multiple\" ng-repeat=\"m in media\" on-finish-render=\"init()\">\n <img ng-if=\"isImage({media: m})\" data-lazy=\"{{m.full || m.thumb || m.images}}\"/>\n <video ng-if=\"isVideo({media: m})\" ng-src=\"{{m.src}}\" type=\"{{m.mimeType}}\" ></video>\n</div>");
SLICK_OPTION_WHITELIST = ['accessiblity', 'autoplay', 'autoplaySpeed', 'arrows', 'cssEase', 'dots', 'draggable', 'fade', 'easing', 'infinite', 'lazyLoad', 'onBeforeChange', 'onAfterChange', 'pauseOnHover', 'responsive', 'slide', 'slidesToShow', 'slidesToScroll', 'speed', 'swipe', 'touchMove', 'touchThreshold', 'vertical'];
SLICK_FUNCTION_WHITELIST = ['slickGoTo', 'slickNext', 'slickPrev', 'slickPause', 'slickPlay', 'slickAdd', 'slickRemove', 'slickFilter', 'slickUnfilter', 'unslick'];
isEmpty = function(value) {
var key;
if (angular.isArray(value)) {
return value.length === 0;
} else if (angular.isObject(value)) {
for (key in value) {
if (value.hasOwnProperty(key)) {
return false;
}
}
}
return true;
};
return {
scope: {
settings: '=',
control: '=',
media: '=',
onDirectiveInit: '&',
isImage: '&',
isVideo: '&'
},
templateUrl: function(tElement, tAttrs) {
if (tAttrs.src) {
return tAttrs.src;
}
return 'angular-slick-carousel/template.html';
},
restrict: 'AE',
terminal: true,
link: function(scope, element, attr) {
var options;
if (typeof attr.isImage !== 'function') {
scope.isImage = function(params) {
//TODO: Should evaluate mimetype of image.. grrrr
//Here is original code
//return params.media.mimeType === 'image/png' || params.media.mimeType === 'image/jpeg';
return true;
};
}
if (typeof attr.isVideo !== 'function') {
scope.isVideo = function(params) {
return params.media.mimeType === 'video/mp4';
};
}
options = scope.settings || {};
angular.forEach(attr, function(value, key) {
if (__indexOf.call(SLICK_OPTION_WHITELIST, key) >= 0) {
return options[key] === scope.$eval(value);
}
});
scope.init = function() {
var slick;
slick = element.slick(options);
scope.internalControl = scope.control || {};
SLICK_FUNCTION_WHITELIST.forEach(function(value) {
scope.internalControl[value] = function() {
slick[value].apply(slick, arguments);
};
});
scope.onDirectiveInit();
};
}
};
}
]);
Answer:
CSS
.slick-slide {
height:200px;
}
.slick-slide img {
height:200px;
}
I was having to set the height eg .slick-carousel{width: 200px;} because adaptive height wasnt working and slick was making the carousel as tall as the imgs (before it was resized by css). but after messing around with it for a while. this is what works for me.
.slick-slide{
display: none;
float: left;
height: auto;
min-height: 1px;
img{
max-width: 100vw !important;
}
}
Try removing slidesToScroll from the config. The following simple combination worked for me without any additional CSS fudgery.
$('.slick-carousel').slick({
variableWidth: true,
centerMode: true
});
Set the container div's height to the desired height for example 60% and the slick's and the 2 following div's height to 100%.
EXAMPLE:
CSS:
.html {
height: 100%;
}
.slick-container {
height: 60%;
}
.slick-slider, .slick-list, .slick-track {
height: 100%;
}
JS:
$(document).ready(function(){
$('.slick-slider').slick();
});
I have faced the same problem more or less, using images in Slick is tricky. So when I had a webpage on desktop everything was very smooth. But on mobile the slide was to small. The suggestion with scale(2) did not work hence it would make the image bigger then te screen.
After going back and forward I decided, to crop the images to be more vertical instead of horizontal.
Then In Jquery I did:
if ($(window).width() < 820) {
$("#slide-1").prop("src", "/slide1-resp.png");
$("#slide-2").prop("src", "/slide2-resp.png");
$("#slide-3").prop("src", "/slide3-resp.png");
}
I hope this answer is relevant for those who came here with the same problem.
Apparently the issue with adaptive height it's a bug as the source code says:
https://github.com/kenwheeler/slick/issues/790

Durandal 2 upgrade redirect issue

Hello and thanks for taking a look at my issue.
I have been migrating my SPA application to use the Durandal 2.0 library, following the sage advice from my oft savior, John Papa. And now that I have completed the upgrade process, I find a strange behavior (or a lack of behavior) when I try to navigate using my menu buttons. Specifically what isn't happening is the browser doesn't redirect to the new page. The interesting thing is that the browser address bar is populated properly and if I simply click in the address bar and press enter (hard reload), I am redirected as expected.
I've looked around and this is not caused due to any security check/redirect which I have seen other discussing elsewhere. Durandal code is unmodified.
js on pages can be quite trivial:
define([], function () {
console.log("welcome loaded");
var vm = {
title: 'Welcome'
};
return vm;
});
So my guess is its something in my configuration of durandal.
main.js:
require.config({
paths: {
'text': '../Scripts/text',
'durandal': '../Scripts/durandal',
'plugins': '../Scripts/durandal/plugins',
'transitions': '../Scripts/durandal/transitions',
'knockout': '../Scripts/knockout-2.3.0',
'bootstrap': '../Scripts/bootstrap',
'jquery': '../Scripts/jquery-1.9.1'
},
shim: {
'bootstrap': {
deps: ['jquery'],
exports: 'jQuery'
}
}
});
define('jquery', function () { return jQuery; });
define('knockout', ko);
define(['durandal/system', 'durandal/app', 'durandal/viewLocator'],
function (system, app, viewLocator) {
// Enable debug message to show in the console
system.debug(true);
app.configurePlugins({
router: true,
dialog: true,
widget: true
});
app.start().then(function () {
toastr.options.positionClass = 'toast-bottom-right';
toastr.options.backgroundpositionClass = 'toast-bottom-right';
// When finding a viewmodel module, replace the viewmodel string
// with view to find it partner view.
viewLocator.useConvention();
// Adapt to touch devices
// app.adaptToDevice();
//Show the app by setting the root view model for our application.
app.setRoot('viewmodels/shell', 'entrance');
});
});
shell.js:
define(['../../Scripts/durandal/plugins/router', 'viewmodels/config', 'services/datacontext'], function (router, config, datacontext) {
function addSession(item) {
router.navigate(item.hash);
}
function boot() {
// $(".page-splash-message").text("Configuring routes...");
router.makeRelative({ moduleId: 'viewmodels' });
router.map(config.routes);
router.buildNavigationModel();
$(".page-splash-message").text("Let's make traxx..!");
return router.activate();
}
function failedInitialization(error) {
var msg = 'App initialization failed: ' + error.message;
}
return {
addSession: addSession,
adminRoutes: adminRoutes,
profileRoutes: profileRoutes,
visitorRoutes: visitorRoutes,
router: router,
activate: function () {
datacontext.primeEditData().then(boot).fail(failedInitialization);
}
};
});
routes in config.js
define(['../../Scripts/durandal/plugins/router'], function (router) {
toastr.options.timeOut = 4000;
toastr.options.positionClass = 'toast-bottom-right';
var startModule = 'Welcome';
var serviceName = 'api/Zepher';
var imageSettings = {
imageBasePath: '../content/images/photos/',
unknownPersonImageSource: 'unknown_person.jpg'
};
var routes = [
{ route: '', moduleId: 'home/welcome', title: 'Welcome', nav: false, },
{ route: 'Welcome', moduleId: 'home/welcome', title: 'Welcome', nav: false, },
{ route: 'NotFound', moduleId: 'home/notFound', title: 'Not Found', nav: false, },
{ route: 'Roadmap', moduleId: 'home/roadmap', title: 'Roadmap', nav: false, },
{ route: 'Register', moduleId: 'account/register', title: 'Register', nav: true, caption: '<i class="fa fa-user"></i> Register' },
{ route: 'RegisterAccounts', moduleId: 'account/registerAccounts', title: 'Register Accounts', nav: false, caption: '<i class="fa fa-key"></i> Register Accounts', },
];
return {
debugEnabled: ko.observable(true),
imageSettings: imageSettings,
servicetitle: serviceName,
startModule: startModule,
router: router,
routes: routes,
activate: function () {
console.log("config activate called");
router.makeRelative({moduleId: 'viewmodels'});
router.map(routes);
router.buildNavigationModel();
//sets up conventional mapping for
//unrecognized routes
router.mapUnknownRoutes('home/nontFound', 'not-found');
//activates the router
return router.activate();
// no longer needs a start module
}
};
});
found what I was missing in my upgrade, so I thought I'd share what I've learned.
Seems I forgot to update my Shell.html file.
From this:
<div>
<header>
<!--ko compose: {view: 'shared/nav', afterCompose: router.afterLogging, transition: 'entrance' } --><!--/ko-->
</header>
<section id="content" class="main">
<!--ko compose: {model: router.activeItem, afterCompose: router.afterCompose, transition: 'entrance', cacheViews: true } --><!--/ko-->
</section>
<footer>
<!--ko compose: {view: 'shared/footer'} --><!--/ko-->
</footer>
</div>
to This:
<div>
<header>
<!--ko compose: {view: 'shared/nav', afterCompose: router.afterLogging, transition: 'entrance' } --><!--/ko-->
</header>
<section id="content" class="main container-fluid page-host" data-bind="router: { transition: 'entrance', cacheViews: true }">
</section>
<footer>
<!--ko compose: {view: 'shared/footer'} --><!--/ko-->
</footer>
</div>

How to set event handler in React sub-component

I'm having trouble getting menu items connected to an event handler. Here's a mock of the UI showing state changes over time. It's a dropdown menu (via Bootstrap), with the root menu item showing the current selection:
[ANN]<click ... [ANN] ... [BOB]<click ... [BOB]
[Ann] [Ann]
[Bob]<click + ajax [Bob]
[Cal] [Cal]
The end goal is to change the page content asynchronously based on the user's selection. Clicking on Bob should trigger the handleClick, but it's not.
As a side note, I'm not terribly happy with the way componentDidMount calls this.handleClick();, but it works for now as a way to get initial menu content from the server.
/** #jsx React.DOM */
var CurrentSelection = React.createClass({
componentDidMount: function() {
this.handleClick();
},
handleClick: function(event) {
alert('clicked');
// Ajax details ommitted since we never get here via onClick
},
getInitialState: function() {
return {title: "Loading items...", items: []};
},
render: function() {
var itemNodes = this.state.items.map(function (item) {
return <li key={item}><a href='#' onClick={this.handleClick}>{item}</a></li>;
});
return <ul className='nav'>
<li className='dropdown'>
<a href='#' className='dropdown-toggle' data-toggle='dropdown'>{this.state.title}</a>
<ul className='dropdown-menu'>{itemNodes}</ul>
</li>
</ul>;
}
});
$(document).ready(function() {
React.renderComponent(
CurrentSelection(),
document.getElementById('item-selection')
);
});
I'm almost positive that my hazy understanding of javascript scoping is to blame, but everything I've tried so far has failed (including trying to pass the handler down through props).
The problem is that you're creating the item nodes using an anonymous function, and inside that this means the window. The fix is to add .bind(this) to the anonymous function.
var itemNodes = this.state.items.map(function (item) {
return <li key={item}><a href='#' onClick={this.handleClick}>{item}</a></li>;
}.bind(this));
Or create a copy of this and use that instead:
var _this = this, itemNodes = this.state.items.map(function (item) {
return <li key={item}><a href='#' onClick={_this.handleClick}>{item}</a></li>;
})
As I can understand the specification of the task for "Anna", "Bob", "Cal, the solution can be the following (based on a react component and ES6):
Basic live demo is here
import React, { Component } from "react"
export default class CurrentSelection extends Component {
constructor() {
super()
this.state = {
index: 0
}
this.list = ["Anna", "Bob", "Cal"]
}
listLi = list => {
return list.map((item, index) => (
<li key={index}>
<a
name={item}
href="#"
onClick={e => this.onEvent(e, index)}
>
{item}
</a>
</li>
))
}
onEvent = (e, index) => {
console.info("CurrentSelection->onEvent()", { [e.target.name]: index })
this.setState({ index })
}
getCurrentSelection = () => {
const { index } = this.state
return this.list[index]
}
render() {
return (
<div>
<ul>{this.listLi(this.list)}</ul>
<div>{this.getCurrentSelection()}</div>
</div>
)
}
}

Resources