I am using Telerik Grid for Blazor WASM.
When data has changed on the server. I get notified via a SignalR connection.
I would like the affected rows to change background color and then return to the normal background color.
Could be a transition to red and fade back to the white or gray color.
I have seen many examples using hover and transitions. But this should be shown without user interaction and preferably delayed on items not in the current view. So when you scroll the grid and the items become visible, the animation starts.
Can AOS https://github.com/michalsnik/aos be used? Or will it only trigger on scroll?
The easiest way for me would be to set a class on the row in the row render event. But it’s a razor page so I can code a custom template.
Whatever can be done using :hover can be done if you add a class (then remove it after the transition). As for the appear only after scroll, you can check for the element is in view using the provided function.
function isScrolledIntoView(el) {
// from https://stackoverflow.com/a/22480938/3807365
var rect = el.getBoundingClientRect();
var elemTop = rect.top;
var elemBottom = rect.bottom;
// Only completely visible elements return true:
var isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
// Partially visible elements return true:
// isVisible = elemTop < window.innerHeight && elemBottom >= 0;
return isVisible;
}
var el = document.querySelector(".row")
window.addEventListener("scroll", function() {
if (isScrolledIntoView(el)) {
if (el.getAttribute("data-did-it")) {
return;
}
el.setAttribute("data-did-it", "true")
el.classList.add("active")
setTimeout(function() {
el.classList.remove("active")
}, 500)
}
})
.row {
transition: 500ms;
background: white;
}
.active {
background: yellow;
}
<div style="height: 400px">
scroll down
</div>
<div class="row">
this is a row
</div>
<div style="height: 400px">
scroll up
</div>
Related
I've been working on this carousel page (that gets started by an "enter" button from the landing page). It flips through just fine until after the first loop, after which the animations stack (until the page overloads).
It's basically an unordered list <ul> within a main <div> that lists images and videos.
IN A NUTSHELL, my animations trigger properly and my classes are transferred well during the first cycle. But by the second loop, something happens. The first page shows up properly, but the second item disappears because the animation that pulls it in is triggered twice (verified by adding an 'animationstart' console log).
THE FUNCTIONS:
Upon entry from the landing page's enterPage() function:
the first item, which is a video, is displayed and played; then
an 'ended' event listener that then triggers a flipCurrentToNext() function.
The flipCurrentToNext() function:
updates the currentPage and a nextPage variables;
pauses the currentPage if it's a video (the firstPage is a video);
adds a transition class that flips the currentPage to the left;
displays and animates the nextPage to flip into the viewport from the right;
it plays the video if it's a video, and just displays, if it's a photo;
adds an 'animationend' eventlistener to the nextPage, which then:
a) removes transition and .current-page class from flipped out currentPage;
b) adds .current-page class to nextPage;
c) updates currentPage = nextPage;
d) handles flipped in page as currentPage;
e) calls a function that autoFlip()s through the carousel.
flipLastToFirst() function does the same as No. 2, except:
nextPage is swapped with firstPage and;
the autoFlip() is not triggered because somehow the enterPage() function still triggers the video 'ended' event listener.
There are click handlers on transparent divs that users can use. I'm still not sure how I want these to override the autoFlip()s. (not really part of my question, but if you do have the answer, I'd love to know it!)
There are also other functions like flipToPrevious(), flipFirstToLast(), but they're basically the same as above.
THE CODE:
function enterPage() {
//some code that deals with the landing page
let cover = document.querySelector('.firstPage');
cover.style.display = "block";
cover.play();
cover.addEventListener('ended', () => {
flipCurrentToNext();
return;
})
return;
//I thought adding return would somehow close the eventlistener.
//I initially explored the possibility that 'ended' was also
//simultaneously calling flipToNext(), causing the
//animation overload.
}
let currentPage =
content.querySelector('.current-page');
let nextPage =
currentPage.parentElement.nextElementSibling.childNodes[1];
function autoFlip() {
currentPage = content.querySelector('.current-page');
if (currentPage.parentElement.nextElementSibling == null) {
flipLastToFirst();
} else {
flipCurrentToNext();
}
clearTimeout();
return;
}
function flipCurrentToNext() {
currentPage =
content.querySelector('.current-page');
nextPage =
currentPage.parentElement.nextElementSibling.childNodes[1];
//updates variables to stay current
if (currentPage.childNodes[1].type == "video/mp4") {
currentPage.pause();
//I suspected if this triggers the 'ended'
//eventlistener. Will it? If it does, how do I go around it?
//I couldn't figure out how to integrate
//a removeEventListener.
currentPage.currentTime = 0;
}
currentPage.classList.add('flipOutLeft')
//This transition class flips the current page out.
nextPage.style.display = "block";
nextPage.style.animation = "flipInRight 0.77s";
//THIS is the animation that gets triggered twice after
//the first loop. Then it stacks. I've console logged
//nextPage, its classes and its IDs. It is one and the same.
//
//This animation is what flips the next page into the viewport.
nextPage.addEventListener('animationend', () => {
currentPage.style.display = "none";
currentPage.classList.remove('flipOutLeft');
currentPage.classList.remove('current-page');
nextPage.classList.add('current-page');
currentPage = nextPage;
//above is an initial solution to handling fast clicks
//not final
if (currentPage.childNodes[1].type == "video/mp4") {
currentPage = content.querySelector('.current-page');
currentPage.play();
currentPage.addEventListener('ended', () => {
autoFlip(); //<--AUTOFLIP CALL for videos
return;
})
} else {
setTimeout(autoFlip, 5555); //<--for photos
}
return;
})
return;
}
//This is the CLICK handler.
flipRight.addEventListener('click', () => {
clearTimeout(autoFlip());
currentPage = content.querySelector('.current-page');
currentPage.removeEventListener('ended', flipCurrentToNext());
//I can't tell if this helped.
if (currentPage.classList.contains('lastPage') == true) {
flipLastToFirst();
} else {
flipCurrentToNext();
}
})
//I've console-logged the hell out of the code.
//I couldn't find where I'm going wrong.
//JUST IN CASE you need to see the LastToFirst function:
function flipLastToFirst() {
currentPage = content.querySelector('.current-page');
let firstPage = currentPage.parentElement.parentElement.firstElementChild.childNodes[1];
if (currentPage.childNodes[1].type == "video/mp4") {
currentPage.pause();
currentPage.currentTime = 0;
muteButton.style.visibility = "hidden";
}
currentPage.classList.add('flipOutLeft');
firstPage.style.display = "block";
firstPage.style.animation = "flipInRight 0.77s";
firstPage.addEventListener('animationend', () => {
currentPage = content.querySelector('.current-page');
currentPage.style.display = "none";
currentPage.classList.remove('flipOutLeft');
currentPage.classList.remove('current-page');
firstPage.classList.add('current-page');
currentPage = content.querySelector('.current-page');
firstPage.play();
muteButton.style.visibility = "visible";
return;
})
return;
}
.carousel__content { /*Unordered List*/
padding: 0;
margin: 0;
list-style-type: none;
overflow: hidden;
height: 100vh;
}
.carousel__page { /*List Item*/
height: 100vh;
float: left;
}
.carousel__page-item {
position: absolute;
display: none;
height: 100vh;
transition: all 0.55s ease-out;
animation: all ease-out;
animation-iteration-count: 1; /*I tried - didn't work*/
}
.flipOutLeft { /*Applied transition*/
transform: translate(-150%, 0);
opacity: 0;
#keyframes flipInRight { /*Applied animation*/
0% {transform: translate(200%, 0)};
100% {transform: translate(0, 0)};
<main>
<!--transparent navigation <buttons>-->
<div>
<ul class="carousel__content">
<li class="carousel__page">
<!--THIS VIDEO is what starts the carousel-->
<video class="carousel__video carousel__page-item
firstPage current-page" preload="auto" autoplay="true">
<source src="" type="video/mp4">
</video>
</li>
<li>
<picture class="carousel__image carousel__page-item">
<img /></picture>
</li>
<!--More are added just like this last picture.-->
</ul>
</div>
</main>
As much as I wanted to keep this short, I also didn't want to withhold relevant information. I obviously can't find the hole. I've been doing a lot of research but I feel like I simply do not see the problem properly.
I started coding a month ago and I definitely feel like I'm in over my head.
P.S. I'm 100% sure there are unnecessary lines in the code - feel free to point them out.
I'm building a simple chat application and I'm facing an issue with setting the scroll position in iOS. It seems to work fine on other platforms, including browser. Ionic v5, using Capacitor, building with Appflow.
The container needs to be scrolled to the bottom by default when the data loads. The page then listens for new messages. If a new message is added then the page should scroll to the bottom if the scroll position is within the bottom 150% of the client height. If not, a FAB appears indicating a new message has appeared and the user can select the button to scroll to the button.
The markup is as so. I have a custom domChange directive which listens to dom changes in the div. I prefer to put the scroll logic here as I was experiencing bugs putting it in the message listener before.
<ion-content>
<div class="chat-container" (click)="containerClick();" (scroll)="scroll($event)" #chatContainer>
<div class="wrapper" *ngIf="messages.length > 0" (domChange)="onDomChange($event)">
<!-- Excuse my inline styling, this is just for debug -->
<div style="width:100%;text-align:center;padding:20px;" *ngIf="loadingMore"><ion-spinner></ion-spinner></div>
<ng-container *ngFor="let message of messages">
<chat-bubble
[sent]="message.from === authService.user.id"
[delivered]="message.delivered"
[position]="message.position"
[timestamp]="message.createdAt.getTime()"
[content]="message.content"
>
</chat-bubble>
</ng-container>
</div>
</div>
<ion-fab vertical="bottom" horizontal="center" slot="fixed" *ngIf="showScrollBtn">
<ion-fab-button size="small" (click)="scrollToBottom()"><ion-icon name="arrow-down-outline"></ion-icon></ion-fab-button>
</ion-fab>
</ion-content>
The CSS is relatively simple but I am use a column-reverse flexbox and I think this might be one of the causes to the issue. The column-reverse means the webview renders the scroll at the bottom by default.
.chat-container {
height: 100%;
flex-grow: 1;
overflow: auto;
display: flex;
flex-direction: column-reverse;
overflow-x: hidden;
padding: 0 16px 0 16px;
}
And then the relevant functions are:
public onDomChange(event: any) {
if(event.addedNodes.length > 0 && event.addedNodes[0].localName == 'chat-bubble') {
const scrollTop = (this.platform.is('ios') && !this.platform.is('mobileweb')) ? this.chatContainer.nativeElement.scrollTop + this.chatContainer.nativeElement.scrollHeight : this.chatContainer.nativeElement.scrollTop;
/*
* Because of the column-reverse iOS sees the bottom of the div as scrollTop = 0 and the top of the div as scrollTop = -scrollHeight
* Android sees it as scrollTop = scrollHeight at the bottom and scrollTop = 0 at the top
*/
const scrollHeight = this.chatContainer.nativeElement.scrollHeight;
const clientHeight = this.chatContainer.nativeElement.clientHeight * 1.5;
const newMessageIndex = this.messages[this.messages.length - 1].index;
if((this.messages.length <= 30 || scrollHeight - scrollTop <= clientHeight) && this.channel.lastConsumedMessageIndex < newMessageIndex) {
this.scrollToBottom();
} else if(this.channel.lastConsumedMessageIndex < newMessageIndex) {
this.showScrollBtn = true;
}
}
}
public scrollToBottom(): Promise<any> {
this.showScrollBtn = false;
this.chatContainer.nativeElement.scrollTop = this.chatContainer.nativeElement.scrollHeight;
return this.channel.setAllMessagesConsumed();
}
The scrollToBottom() function works absolutely fine when triggered by the button click event but doesn't seem to do anything when triggered in the domChange trigger. I think this is because the trigger is called before the chat bubble element has rendered but I'm not sure how to fix this.
Would appreciate any ideas/thoughts
Thanks in advance
I tried to change the height of the dragged object using angular material drag and drop. I have tried to change the class name of the dragged div using cdkdragstart event and adjusted the height in the css.
app.component.html
<div cdkDropList #nameDragDrop="cdkDropList" [cdkDropListData]="names" [cdkDropListConnectedTo]="['nameTime']" (cdkDropListDropped)="drop($event)">
<div *ngFor="let iteration = index" cdkDrag (cdkDragMoved)="dragStarted($event,iteration)" (cdkDragEnded)="dragEnded(iteration)">
<div class="nameDiv" [ngClass]="{'reduceHeight': hideImageIcon === iteration, 'scenes': hideImageIcon !== iteration }">
</div>
</div>
</div
app.component.ts
dragStarted(event,index:any) {
this.hideImageIcon = index;
}
drop(event: CdkDragDrop<String[]>) {
if (event.previousContainer.id === event.container.id) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
} else {
transferArrayItem(event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex);
}
}
I tried to get the dragged index using drag started method and assigned the index to the variable and change the class name in html file based on the condition.
app.component.css
.reduceHeight{
height:27px!important;
}
.show{
display: block!important;
}
.hide{
display: none!important;
}
Here the problem is, height is not getting changed while dropping the item. I want to change the height of the dragged object on start of the drag event. Is there any event is available in angular material drag and drop to change the height of the dragged object?
How about customizing drag preview https://material.angular.io/cdk/drag-drop/overview#customizing-the-drag-preview
Here is my code, where I'm trying to detect the element, which a jQuery UI draggable is hovering over. I need to get the element's object and attributes, such as class names (in this case .sortable-grid,.sortable-table,.sortable-row,.sortable-cell).
The answers found here only show how to get the draggable item itself (ui.helper or event.target), but not the element it is hovering above.
The best way to answer would be using the prepared JSFiddle, since my code uses an iframe, which would not work if the full code is posted here:
JSFiddle
HTML:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.0-beta.1/jquery-ui.js"></script>
<div style="background-color:grey;display:inline;cursor:move" id="draggable">DRAG ME</div>
<iframe src="https://fiddle.jshell.net/piglin/UAcC7/1869/show/" id="frame" style="width:100%;overflow:visible" seamless="seamless" scrolling="no"></iframe>
JS:
$("#draggable").draggable({
drag: function(event, ui) {
//Some code here
}
}
It was possible by modifying the function from another answer to fit this purpose. After adapting it to use the contentWindow of the iframe and adding offset calculation it works now.
Solution
function allElementsFromPointIframe(x, y, offsetX, offsetY) {
var element, elements = [];
var old_visibility = [];
while (true) {
element = document.getElementById('frame').contentWindow.document.elementFromPoint(x - offsetX, y - offsetY);
if (!element || element === document.getElementById('frame').contentWindow.document.documentElement) {
break;
}
elements.push(element);
old_visibility.push(element.style.visibility);
element.style.visibility = 'hidden'; // Temporarily hide the element (without changing the layout)
}
for (var k = 0; k < elements.length; k++) {
elements[k].style.visibility = old_visibility[k];
}
elements.reverse();
return elements;
}
var selected = $('');
var tmpColor = 'transparent';
$("#draggable").draggable({
drag: function(event, ui) {
var el = $(allElementsFromPointIframe(event.pageX, event.pageY, $(frame).offset().left, $(frame).offset().top));
var div = $(el).filter('ul, li').not($(this));
selected.css({'backgroundColor': tmpColor});
selected = div.last()
tmpColor = selected.css('backgroundColor');
selected.css({'backgroundColor': 'red'});
console.dir(div);
},
iframeFix: true,
iframeOffset: $('#iframe').offset()
});
I'm looking to do a slide-card style website with html/css/js.
I have seen some nice examples like:
http://www.thepetedesign.com/demos/onepage_scroll_demo.html
http://alvarotrigo.com/fullPage/
However, what these DON'T seem to do is slide a page out WHILE the page underneath is visible, as if they were a stack of index cards. Parallax scrolling does this, but it typically will wipe the existing area, rather then scroll/move it off screen. Any ideas?
Here is a fiddle using JQuery that does something like what you are looking for, you could implement it with that one scroll effect of the card sliders and have it animate in probably.
http://jsfiddle.net/d6rKn/
(function(window){ $.fn.stopAtTop= function () {
var $this = this,
$window = $(window),
thisPos = $this.offset().top,
setPosition,
under,
over;
under = function(){
if ($window.scrollTop() < thisPos) {
$this.css({
position: 'absolute',
top: ""
});
setPosition = over;
}
};
over = function(){
if (!($window.scrollTop() < thisPos)){
$this.css({
position: 'fixed',
top: 0
});
setPosition = under;
}
};
setPosition = over;
$window.resize(function()
{
bumperPos = pos.offset().top;
thisHeight = $this.outerHeight();
setPosition();
});
$window.scroll(function(){setPosition();});
setPosition();
};
})(window);
$('#one').stopAtTop();
$('#two').stopAtTop();
$('#three').stopAtTop();
$('#four').stopAtTop();
See the fiddle for HTML and CSS.
Not my fiddle just grabbed it with a quick google search.