How to remove some portion of an image - javascript

I am trying to build a component, where it takes images and the first image will be shown while remaining images will be shown in a half circle.
Here I am trying to clip the image portion from second image by adding a class which is not working. I need to get the images as shown in the attached image below.
Images.js
const Images = ({ images = [] }) => {
return (
<div>
{images?.map((image, index) => {
const isFirst = index === 0;
return (
<img
src={image.url}
alt={image.title}
key={image.title}
className={isFirst ? "" : "semi-circle"}
/>
);
})}
</div>
);
};
export default Images;
style.css
img {
border-radius: 50%;
width: 100px;
height: 100px;
}
.semi-circle {
clip: rect(0px, 25px, 25px, 0px);
}
What am I doing wrong here.
Sandbox
See the image that illustrates what I'm trying to achieve:
this

You can use clip-path: path() to create custom shapes. This is now supported in modern browsers.
.semi-circle {
clip-path: path('m27.215 9.6962c70.511-13.195 81.235 89.479 4.948 82.882 16.494-5.773 43.709-54.43-4.948-82.882z');
}
Updated Sandbox
Clip-path Caniuse.com

Related

How can I "lock" an HTML canvas's aspect ratio to an image's aspect ratio and allow the canvas to resize in its parent div using ReactJS?

I am building a desktop app with ReactJS, Electron, and Material UI. I have a densely populated UI, as I am managing inventory from a database. Since I want my app to be resizable, almost every UI element is in a CSS flex box. I have a parent "Paper" element from Material UI (think of it as a div with a background color) that is resizing correctly. I want to load an image from the web into that "Paper", maintaining the image's original aspect ratio while allowing it to grow and shrink with its parent "Paper" div. From what I've read on Stack Overflow and Google so far, I understand I can use an HTML canvas element to load the image from the web. This is working properly. I've also found some CSS from a blog that will scale the image at its original aspect ratio properly, but only in one dimension (width or height, but not both). This mostly works, except the canvas horizontally overlaps its parent "Paper" div when the window is horizontally narrowed. How can I make an autoscaling canvas that resizes in both dimensions while not exceeding the bounds of its parent div with ReactJS? Is there a different component besides a canvas that would accomplish this in an easier way? I can't set hard values for width and height of the parent div since its in a flex box. Additionally, I would like the image to be horizontally and vertically centered in its parent div, which is not currently working. This is what I have so far:
In my CSS file for the class:
canvas {
position: absolute;
height: 100%;
width: auto;
top: 0;
left: 0;
}
.styled-paper {
textAlign: 'center',
backgroundColor: 'rgba(49, 51, 55, 1)',
position: 'relative',
width: '100%',
/* this is to vertically align this div with two smaller divs next to it */
height: '87%',
border: 1,
borderColor: 'rgba(108, 109, 112, 1)',
borderRadius: 8,
borderStyle: 'solid',
borderWidth: '1px',
}
In the TSX file for the class:
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import TextField from '#material-ui/core/TextField';
import { isNull, isUndefined } from 'underscore/modules/index';
function isBlank(str: string | undefined) {
return (isUndefined(str) || isNull(str) || str.trim().length === 0);
}
async function LoadImageFromUrl(url: string): Promise<string> {
//URL shortening for database
let shortenedURL: string;
if (url.startsWith("https://tinyurl.com/")) {
shortenedURL = url;
} else {
//Other async function that shortens URL with TinyURL
shortenedURL = await ShortenURL(url);
}
//Get reference to canvas and its context
let canvas = document.getElementById("itemImageCanvas") as HTMLCanvasElement;
let context = canvas.getContext("2d");
//
let itemImg = new Image();
itemImg.onload = function() {
var wrh = itemImg.width / itemImg.height;
var newWidth = canvas.width;
var newHeight = newWidth / wrh;
if (newHeight > canvas.height) {
newHeight = canvas.height;
newWidth = newHeight * wrh;
}
context!.drawImage(itemImg, 0, 0, newWidth, newHeight);
}
itemImg.src = shortenedURL;
return shortenedURL;
}
export default class ItemInfoEditPane extends Component<any, any> {
state = {
imageURL: '',
}
onChange = (event: any) => this.setState({ [event.target.name] : event.target.value });
onImageURLChange = (newUrl: string) => this.setState({imageURL: newUrl});
updateImageURL = (callback: Function) => {
LoadImageFromUrl(this.state.imageURL).then(function(result) { callback(result) });
}
render() {
const {
imageURL,
} = this.state;
return(
//Lots of parent elements and divs/flex box divs I removed for clarity that this block is nested in
<div className="hRow textField triple">
<Paper className="styled-paper" elevation={0}>
<canvas id="itemImageCanvas"/>
</Paper>
</div>
//Other elements between these two, but this text field is where I grab the image URL from
<div className='hRow textField'>
<TextField name="imageURL" label="Image URL" value={imageURL} onChange={this.onChange}
onBlur={() => { if (!isBlank(imageURL)) { this.updateImageURL(this.onImageURLChange) }}}/>
</div>
)
}
}
To resize keeping your aspect ratio, try setting #itemImageCanvas{ width: 100%; } without touching it's height manually, then on your component:
Render the canvas with default size (300, 150), saved on your state.
render() {
return(
<div>
<canvas id="itemImageCanvas"
width={this.state.canvasWidth}
height={this.state.canvasHeight} />
</div>
)
}
Use se component's life cycle methods to load the image, and resize.
componentDidMount() {
let image = new Image();
image.src = 'img.jpg';
image.onload = () => {
this.setState((state) => ({
...state,
canvasWidth: image.width,
canvasHeight: image.height
}));
document.getElementById('itemImageCanvas')
.getContext('2d')
.drawImage(image, 0, 0);
};
}
Using pieces of this answer, I was able to solve my issue. The CSS now looks like this:
canvas {
position: absolute;
max-height: 100%;
max-width: 100%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.styled-paper: {
textAlign: 'center',
width: '100%',
height: '87%',
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}
Setting max-height and max-width allows the canvas to grow up to the parent's full size but not exceed it. Setting the centering properties in the styled-paper section and the top, left, and transform: translate properties in the canvas section allow the canvas to be horizontally and vertically centered while growing and shrinking. It also keeps the image's original aspect ratio. The JavaScript now looks like this:
export default class ItemInfoEditPane extends Component<any, any> {
state = {
imageURL: '',
canvasWidth: 0,
canvasHeight: 0,
}
onChange = (event: any) => this.setState({ [event.target.name] : event.target.value });
updateImageURL = () => {
let image = new Image();
image.onload = () => {
this.setState((state: any) => ({
...state,
canvasWidth: image.width,
canvasHeight: image.height
}));
(document.getElementById('itemImageCanvas') as HTMLCanvasElement).getContext('2d').drawImage(image, 0, 0);
};
image.src = this.state.imageURL;
}
render() {
const {
imageURL,
canvasWidth,
canvasHeight,
} = this.state;
return(
<div className="styled-paper">
<canvas id="itemImageCanvas" width={canvasWidth} height={canvasHeight}/>
</div>
<div>
<TextField name="imageURL" label="Image URL" value={imageURL} onChange={this.onChange}
onBlur={() => { if (!isBlank(imageURL)) { this.updateImageURL() }}}/>
</div>
)
}
}
Setting the canvas's width and height with JavaScript allows the canvas to be the aspect ratio of the image. This, combined with the max width and height properties in the CSS, is what allows the image to scale up to the size of the parent div, but not larger than it. In my case, a user can paste a URL to an image from the web into a textfield and my app will load this image into the canvas. If you want to add padding so that the image doesn't quite go all the way to the edge of its parent div, you can set the max-width and max-height properties of the canvas to something like 95%.

Change style header/nav with Intersection Observer (IO)

Fiddle latest
I started this question with the scroll event approach, but due to the suggestion of using IntersectionObserver which seems much better approach i'm trying to get it to work in that way.
What is the goal:
I would like to change the style (color+background-color) of the header depending on what current div/section is observed by looking for (i'm thinking of?) its class or data that will override the default header style (black on white).
Header styling:
font-color:
Depending on the content (div/section) the default header should be able to change the font-color into only two possible colors:
black
white
background-color:
Depending on the content the background-color could have unlimited colors or be transparent, so would be better to address that separate, these are the probably the most used background-colors:
white (default)
black
no color (transparent)
CSS:
header {
position: fixed;
width: 100%;
top: 0;
line-height: 32px;
padding: 0 15px;
z-index: 5;
color: black; /* default */
background-color: white; /* default */
}
Div/section example with default header no change on content:
<div class="grid-30-span g-100vh">
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
data-src="/images/example_default_header.jpg"
class="lazyload"
alt="">
</div>
Div/section example change header on content:
<div class="grid-30-span g-100vh" data-color="white" data-background="darkblue">
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
data-src="/images/example_darkblue.jpg"
class="lazyload"
alt="">
</div>
<div class="grid-30-span g-100vh" data-color="white" data-background="black">
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
data-src="/images/example_black.jpg"
class="lazyload"
alt="">
</div>
Intersection Observer approach:
var mq = window.matchMedia( "(min-width: 568px)" );
if (mq.matches) {
// Add for mobile reset
document.addEventListener("DOMContentLoaded", function(event) {
// Add document load callback for leaving script in head
const header = document.querySelector('header');
const sections = document.querySelectorAll('div');
const config = {
rootMargin: '0px',
threshold: [0.00, 0.95]
};
const observer = new IntersectionObserver(function (entries, self) {
entries.forEach(entry => {
if (entry.isIntersecting) {
if (entry.intersectionRatio > 0.95) {
header.style.color = entry.target.dataset.color !== undefined ? entry.target.dataset.color : "black";
header.style.background = entry.target.dataset.background !== undefined ? entry.target.dataset.background : "white";
} else {
if (entry.target.getBoundingClientRect().top < 0 ) {
header.style.color = entry.target.dataset.color !== undefined ? entry.target.dataset.color : "black";
header.style.background = entry.target.dataset.background !== undefined ? entry.target.dataset.background : "white";
}
}
}
});
}, config);
sections.forEach(section => {
observer.observe(section);
});
});
}
Instead of listening to scroll event you should have a look at Intersection Observer (IO).
This was designed to solve problems like yours. And it is much more performant than listening to scroll events and then calculating the position yourself.
First, here is a codepen which shows a solution for your problem.
I am not the author of this codepen and I would maybe do some things a bit different but it definitely shows you the basic approach on how to solve your problem.
Things I would change: You can see in the example that if you scoll 99% to a new section, the heading changes even tough the new section is not fully visible.
Now with that out of the way, some explaining on how this works (note, I will not blindly copy-paste from codepen, I will also change const to let, but use whatever is more appropriate for your project.
First, you have to specify the options for IO:
let options = {
rootMargin: '-50px 0px -55%'
}
let observer = new IntersectionObserver(callback, options);
In the example the IO is executing the callback once an element is 50px away from getting into view. I can't recommend some better values from the top of my head but if I would have the time I would try to tweak these parameters to see if I could get better results.
In the codepen they define the callback function inline, I just wrote it that way to make it clearer on what's happening where.
Next step for IO is to define some elements to watch. In your case you should add some class to your divs, like <div class="section">
let entries = document.querySelectorAll('div.section');
entries.forEach(entry => {observer.observe(entry);})
Finally you have to define the callback function:
entries.forEach(entry => {
if (entry.isIntersecting) {
//specify what should happen if an element is coming into view, like defined in the options.
}
});
Edit: As I said this is just an example on how to get you started, it's NOT a finished solution for you to copy paste. In the example based on the ID of the section that get's visible the current element is getting highlighted. You have to change this part so that instead of setting the active class to, for example, third element you set the color and background-color depending on some attribute you set on the Element. I would recommend using data attributes for that.
Edit 2: Of course you can continue using just scroll events, the official Polyfill from W3C uses scroll events to emulate IO for older browsers.it's just that listening for scroll event and calculating position is not performant, especially if there are multiple elements. So if you care about user experience I really recommend using IO. Just wanted to add this answer to show what the modern solution for such a problem would be.
Edit 3: I took my time to create an example based on IO, this should get you started.
Basically I defined two thresholds: One for 20 and one for 90%. If the element is 90% in the viewport then it's save to assume it will cover the header. So I set the class for the header to the element that is 90% in view.
Second threshold is for 20%, here we have to check if the element comes from the top or from the bottom into view. If it's visible 20% from the top then it will overlap with the header.
Adjust these values and adapt the logic as you see.
const sections = document.querySelectorAll('div');
const config = {
rootMargin: '0px',
threshold: [.2, .9]
};
const observer = new IntersectionObserver(function (entries, self) {
entries.forEach(entry => {
if (entry.isIntersecting) {
var headerEl = document.querySelector('header');
if (entry.intersectionRatio > 0.9) {
//intersection ratio bigger than 90%
//-> set header according to target
headerEl.className=entry.target.dataset.header;
} else {
//-> check if element is coming from top or from bottom into view
if (entry.target.getBoundingClientRect().top < 0 ) {
headerEl.className=entry.target.dataset.header;
}
}
}
});
}, config);
sections.forEach(section => {
observer.observe(section);
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.g-100vh {
height: 100vh
}
header {
min-height: 50px;
position: fixed;
background-color: green;
width: 100%;
}
header.white-menu {
color: white;
background-color: black;
}
header.black-menu {
color: black;
background-color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<header>
<p>Header Content </p>
</header>
<div class="grid-30-span g-100vh white-menu" style="background-color:darkblue;" data-header="white-menu">
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
data-src="/images/example_darkblue.jpg"
class="lazyload"
alt="<?php echo $title; ?>">
</div>
<div class="grid-30-span g-100vh black-menu" style="background-color:lightgrey;" data-header="black-menu">
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
data-src="/images/example_lightgrey.jpg"
class="lazyload"
alt="<?php echo $title; ?>">
</div>
I might not understand the question completely, but as for your example - you can solve it by using the mix-blend-mode css property without using javascript at all.
Example:
header {background: white; position: relative; height: 20vh;}
header h1 {
position: fixed;
color: white;
mix-blend-mode: difference;
}
div {height: 100vh; }
<header>
<h1>StudioX, Project Title, Category...</h1>
</header>
<div style="background-color:darkblue;"></div>
<div style="background-color:lightgrey;"></div>
I've encountered the same situation and the solution I implemented is very precise because it doesn't rely on percentages but on real elements' bounding boxes:
class Header {
constructor() {
this.header = document.querySelector("header");
this.span = this.header.querySelector('span');
this.invertedSections = document.querySelectorAll(".invertedSection");
window.addEventListener('resize', () => this.resetObserver());
this.resetObserver();
}
resetObserver() {
if (this.observer) this.observer.disconnect();
const {
top,
height
} = this.span.getBoundingClientRect();
this.observer = new IntersectionObserver(entries => this.observerCallback(entries), {
root: document,
rootMargin: `-${top}px 0px -${window.innerHeight - top - height}px 0px`,
});
this.invertedSections.forEach((el) => this.observer.observe(el));
};
observerCallback(entries) {
let inverted = false;
entries.forEach((entry) => {
if (entry.isIntersecting) inverted = true;
});
if (inverted) this.header.classList.add('inverted');
else this.header.classList.remove('inverted');
};
}
new Header();
header {
position: fixed;
top: 0;
left: 0;
right: 0;
padding: 20px 0;
text-transform: uppercase;
text-align: center;
font-weight: 700;
}
header.inverted {
color: #fff;
}
section {
height: 500px;
}
section.invertedSection {
background-color: #000;
}
<body>
<header>
<span>header</span>
</header>
<main>
<section></section>
<section class="invertedSection"></section>
<section></section>
<section class="invertedSection"></section>
</main>
</body>
What it does is actually quite simple: we can't use IntersectionObserver to know when the header and other elements are crossing (because the root must be a parent of the observed elements), but we can calculate the position and size of the header to add rootMargin to the observer.
Sometimes, the header is taller than its content (because of padding and other stuff) so I calculate the bounding-box of the span in the header (I want it to become white only when this element overlaps a black section).
Because the height of the window can change, I have to reset the IntersectionObserver on window resize.
The root property is set to document here because of iframe restrictions of the snippet (otherwise you can leave this field undefined).
With the rootMargin, I specify in which area I want the observer to look for intersections.
Then I observe every black section. In the callback function, I define if at least one section is overlapping, and if this is true, I add an inverted className to the header.
If we could use values like calc(100vh - 50px) in the rootMargin property, we may not need to use the resize listener.
We could even improve this system by adding side rootMargin, for instance if I have black sections that are only half of the window width and may or may not intersect with the span in the header depending on its horizontal position.
#Quentin D
I searched the internet for something like this, and I found this code to be the best solution for my needs.
Therefore I decided to build on it and create a universal "Observer" class, that can be used in many cases where IntesectionObserver is required, including changing the header styles.
I haven't tested it much, only in a few basic cases, and it worked for me. I haven't tested it on a page that has a horizontal scroll.
Having it this way makes it easy to use it, just save it as a .js file and include/import it in your code, something like a plugin. :)
I hope someone will find it useful.
If someone finds better ideas (especially for "horizontal" sites), it would be nice to see them here.
Edit: I hadn't made the correct "unobserve", so I fixed it.
/* The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
ROOT:
It is not necessary for the root to be the ancestor element of the target. The root is allways the document, and the so-called root element is used only to get its size and position, to create an area in the document, with options.rootMargin.
Leave it false to have the viewport as root.
TARGET:
IntersectionObserver triggers when the target is entering at the specified ratio(s), and when it exits at the same ratio(s).
For more on IntersectionObserverEntry object, see:
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#targeting_an_element_to_be_observed
IntersectionObserverEntry.time // Timestamp when the change occurred
IntersectionObserverEntry.rootBounds // Unclipped area of root
IntersectionObserverEntry.intersectionRatio // Ratio of intersectionRect area to boundingClientRect area
IntersectionObserverEntry.target // the Element target
IntersectionObserverEntry.boundingClientRect // target.boundingClientRect()
IntersectionObserverEntry.intersectionRect // boundingClientRect, clipped by its containing block ancestors, and intersected with rootBounds
THRESHOLD:
Intersection ratio/threshold can be an array, and then it will trigger on each value, when in and when out.
If root element's size, for example, is only 10% of the target element's size, then intersection ratio/threshold can't be set to more than 10% (that is 0.1).
CALLBACKS:
There can be created two functions; when the target is entering and when it's exiting. These functions can do what's required for each event (visible/invisible).
Each function is passed three arguments, the root (html) element, IntersectionObserverEntry object, and intersectionObserver options used for that observer.
Set only root and targets to only have some info in the browser's console.
For more info on IntersectionObserver see: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
Polyfill: <script src="https://polyfill.io/v3/polyfill.js?features=IntersectionObserver"></script>
or:
https://github.com/w3c/IntersectionObserver/tree/main/polyfill
Based on answer by Quentin D, answered Oct 27 '20 at 12:12
https://stackoverflow.com/questions/57834100/change-style-header-nav-with-intersection-observer-io
root - (any selector) - root element, intersection parent (only the first element is selected).
targets - (any selector) - observed elements that trigger function when visible/invisible.
inCb - (function name) - custom callback function to trigger when the target is intersecting.
outCb - (function name) - custom callback function to trigger when the target is not intersecting.
thres - (number 0-1) - threshold to trigger the observer (e.g. 0.1 will trigger when 10% is visible).
unobserve- (bolean) - if true, the target is unobserved after triggering the callback.
EXAMPLE:
(place in 'load' event listener, to have the correct dimensions)
var invertedHeader = new Observer({
root: '.header--main', // don't set to have the viewport as root
targets: '[data-bgd-dark]',
thres: [0, .16],
inCb: someCustomFunction,
});
*/
class Observer {
constructor({
root = false,
targets = false,
inCb = this.isIn,
outCb = this.isOut,
thres = 0,
unobserve = false,
} = {}) {
// this element's position creates with rootMargin the area in the document
// which is used as intersection observer's root area.
// the real root is allways the document.
this.area = document.querySelector(root); // intersection area
this.targets = document.querySelectorAll(targets); // intersection targets
this.inCallback = inCb; // callback when intersecting
this.outCallback = outCb; // callback when not intersecting
this.unobserve = unobserve; // unobserve after intersection
this.margins; // rootMargin for observer
this.windowW = document.documentElement.clientWidth;
this.windowH = document.documentElement.clientHeight;
// intersection is being checked like:
// if (entry.isIntersecting || entry.intersectionRatio >= this.ratio),
// and if ratio is 0, "entry.intersectionRatio >= this.ratio" will be true,
// even for non-intersecting elements, therefore:
this.ratio = thres;
if (Array.isArray(thres)) {
for (var i = 0; i < thres.length; i++) {
if (thres[i] == 0) {
this.ratio[i] = 0.0001;
}
}
} else {
if (thres == 0) {
this.ratio = 0.0001;
}
}
// if root selected use its position to create margins, else no margins (viewport as root)
if (this.area) {
this.iArea = this.area.getBoundingClientRect(); // intersection area
this.margins = `-${this.iArea.top}px -${(this.windowW - this.iArea.right)}px -${(this.windowH - this.iArea.bottom)}px -${this.iArea.left}px`;
} else {
this.margins = '0px';
}
// Keep this last (this.ratio has to be defined before).
// targets are required to create an observer.
if (this.targets) {
window.addEventListener('resize', () => this.resetObserver());
this.resetObserver();
}
}
resetObserver() {
if (this.observer) this.observer.disconnect();
const options = {
root: null, // null for the viewport
rootMargin: this.margins,
threshold: this.ratio,
}
this.observer = new IntersectionObserver(
entries => this.observerCallback(entries, options),
options,
);
this.targets.forEach((target) => this.observer.observe(target));
};
observerCallback(entries, options) {
entries.forEach(entry => {
// "entry.intersectionRatio >= this.ratio" for older browsers
if (entry.isIntersecting || entry.intersectionRatio >= this.ratio) {
// callback when visible
this.inCallback(this.area, entry, options);
// unobserve
if (this.unobserve) {
this.observer.unobserve(entry.target);
}
} else {
// callback when hidden
this.outCallback(this.area, entry, options);
// No unobserve, because all invisible targets will be unobserved automatically
}
});
};
isIn(rootElmnt, targetElmt, options) {
if (!rootElmnt) {
console.log(`IO Root: VIEWPORT`);
} else {
console.log(`IO Root: ${rootElmnt.tagName} class="${rootElmnt.classList}"`);
}
console.log(`IO Target: ${targetElmt.target.tagName} class="${targetElmt.target.classList}" IS IN (${targetElmt.intersectionRatio * 100}%)`);
console.log(`IO Threshold: ${options.threshold}`);
//console.log(targetElmt.rootBounds);
console.log(`============================================`);
}
isOut(rootElmnt, targetElmt, options) {
if (!rootElmnt) {
console.log(`IO Root: VIEWPORT`);
} else {
console.log(`IO Root: ${rootElmnt.tagName} class="${rootElmnt.classList}"`);
}
console.log(`IO Target: ${targetElmt.target.tagName} class="${targetElmt.target.classList}" IS OUT `);
console.log(`============================================`);
}
}
This still needs adjustment, but you could try the following:
const header = document.getElementsByTagName('header')[0];
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
header.style.color = entry.target.dataset.color || '';
header.style.backgroundColor = entry.target.dataset.background;
}
});
}, { threshold: 0.51 });
[...document.getElementsByClassName('observed')].forEach((t) => {
t.dataset.background = t.dataset.background || window.getComputedStyle(t).backgroundColor;
observer.observe(t);
});
body {
font-family: arial;
margin: 0;
}
header {
border-bottom: 1px solid red;
margin: 0 auto;
width: 100vw;
display: flex;
justify-content: center;
position: fixed;
background: transparent;
transition: all 0.5s ease-out;
}
header div {
padding: 0.5rem 1rem;
border: 1px solid red;
margin: -1px -1px -1px 0;
}
.observed {
height: 100vh;
border: 1px solid black;
}
.observed:nth-of-type(2) {
background-color: grey;
}
.observed:nth-of-type(3) {
background-color: white;
}
<header>
<div>One</div>
<div>Two</div>
<div>Three</div>
</header>
<div class="observed">
<img src="http://placekitten.com/g/200/300">
<img src="http://placekitten.com/g/400/300">
</div>
<div class="observed" data-color="white" data-background="black">
<img src="http://placekitten.com/g/600/300">
</div>
<div class="observed" data-color="black" data-background="white">
<img src="http://placekitten.com/g/600/250">
</div>
The CSS ensures each observed section takes up 100vw and the observer does its thing when anyone of those comes into view by at least 51% percent.
In the callback the headers background-color is then set to the background-color of the intersecting element.

Changing background-image property causes a flicker in Firefox

I'm working on a component that rotates a series of background images in a banner on my page. The problem I'm running into is that when the background-image properties url is changed via state it seems to cause a flash of white. This flashing doesn't seem to happen all the time in Chrome, but does happen consistently in Firefox and sometimes Safari. For additional context I'm using Mac OSX.
At first I assumed this was because the images are being retrieved by the browser when they are requested, but to avoid this I've made some considerations for pre-fetching by rendering a hidden image tag with the resource.
{this.props.items.map(item => (
<img src={item} style={{ display: "none" }} />
))}
I've also tried creating a new image in the rotate method that pre-fetches the next rotation item ahead of the transition, but neither seem to work.
const img = new Image();
img.src = this.props.items[index + 1];
Where am I going wrong here? I've attached an example of the component below. Any help would be appreciated.
class Cats extends React.Component {
constructor(props) {
super(props);
this.state = {
background: props.items[0],
index: 0
};
this.rotate = this.rotate.bind(this);
}
// Let's you see when the component has updated.
componentDidMount() {
this.interval = setInterval(() => this.rotate(), 5000);
}
componentDidUnmount() {
clearInterval(this.interval);
}
rotate() {
const maximum = this.props.items.length - 1;
const index = this.state.index === maximum ? 0 : this.state.index + 1;
this.setState({
background: this.props.items[index],
index
});
}
render() {
return (
<div
className="background"
style={{ backgroundImage: `url(${this.state.background})` }}
>
{this.props.items.map(item => (
<img src={item} style={{ display: "none" }} />
))}
</div>
);
}
}
ReactDOM.render(
<Cats
items={[
"https://preview.redd.it/8lt2w3du0zb31.jpg?width=640&crop=smart&auto=webp&s=58d0eb6771296b3016d85ee1828d1c26833fd022",
"https://preview.redd.it/120qmpjmg1c31.jpg?width=640&crop=smart&auto=webp&s=1b01fc0c3f20098e6bb1f4126c3c2a54b7bc2b8e",
"https://preview.redd.it/guprqpenoxb31.jpg?width=640&crop=smart&auto=webp&s=ace24e96764bb40a01e7d167a88d35298db76a1c",
"https://preview.redd.it/mlzq0x1o0xb31.jpg?width=640&crop=smart&auto=webp&s=b3fd159069f45b6c354de975daffde21f04c3ad5"
]}
/>,
document.querySelector(".wrapper")
);
html, body, .wrapper {
width: 100%;
height: 100%;
}
.background {
position: static;
background-size: cover;
height: 100%;
width: 100%;
transition: background-image 1s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.1/react-dom.min.js"></script>
<div class="wrapper"></div>
Unfortunately, it seems like this flicker is a known bug in Firefox caused by its image decoder, which won't decode an image until it's displayed for the first time. In the snippet below, I created overlapping divs, one which loads the next image slightly earlier and sits behind the other. This way when the other "flickers," the proper image is already displayed behind, rather than a white background.
You could also theoretically display all the images in the hidden div really quickly, then set it back to white, since the images only need to be displayed once for the decoder to work.
Depending on the long-term goal for this project, the most proper way around this problem may be to use a <canvas> to render your images. The canvas element uses a different decoder which won't cause a flicker.
class Cats extends React.Component {
constructor(props) {
super(props);
this.props.items.forEach((item) => {
const img = new Image(640, 640);
img.src = item;
});
this.state = {
background: props.items[0],
preloadBackground: props.items[1],
index: 0
};
this.rotate = this.rotate.bind(this);
}
// Let's you see when the component has updated.
componentDidMount() {
this.interval = setInterval(() => this.rotate(), 5000);
}
componentDidUnmount() {
clearInterval(this.interval);
}
rotate() {
const maximum = this.props.items.length - 1;
const index = this.state.index === maximum ? 0 : this.state.index + 1;
this.setState({
preloadBackground: this.props.items[index],
index
});
setTimeout(() => {
this.setState({
background: this.props.items[index],
});
}, 100);
}
render() {
return (
<div className="pane">
<div
className="preload-background"
style={{ backgroundImage: `url(${this.state.preloadBackground})` }}
>
</div>
<div
className="background"
style={{ backgroundImage: `url(${this.state.background})` }}
>
</div>
</div>
);
}
}
ReactDOM.render(
<Cats
items={[
"https://preview.redd.it/8lt2w3du0zb31.jpg?width=640&crop=smart&auto=webp&s=58d0eb6771296b3016d85ee1828d1c26833fd022",
"https://preview.redd.it/120qmpjmg1c31.jpg?width=640&crop=smart&auto=webp&s=1b01fc0c3f20098e6bb1f4126c3c2a54b7bc2b8e",
"https://preview.redd.it/guprqpenoxb31.jpg?width=640&crop=smart&auto=webp&s=ace24e96764bb40a01e7d167a88d35298db76a1c",
"https://preview.redd.it/mlzq0x1o0xb31.jpg?width=640&crop=smart&auto=webp&s=b3fd159069f45b6c354de975daffde21f04c3ad5"
]}
/>,
document.querySelector(".wrapper")
);
html, body, .wrapper, .pane {
width: 100%;
height: 100%;
}
.background {
position: static;
background-size: cover;
height: 100%;
width: 100%;
transition: background-image 1s ease-in-out;
}
.preload-background {
position: absolute;
background-size: cover;
height: 100%;
width: 100%;
z-index: -1;
transition: background-image 1s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.1/react-dom.min.js"></script>
<div class="wrapper"></div>
You can use decode() method that will let you know when image is decoded and ready to be used.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode
In your case:
const img = new Image();
img.src = this.props.items[index + 1];
img.decode()
.then(() => {
// image is decoded and ready to use
})
.catch((encodingError) => {
// do something with the error.
})

how to animate image in react when image change after some interval?

I am trying to make a image slider in react in which a image is change after 5000 second.
I checked from here http://mumbaimirror.indiatimes.com/ .where this website implement that functionality .
I tried to implement same this in react .I am able to make that , but my image is not slide(from right to left) in other words image not showing animation when second image show n view
here is my code
https://codesandbox.io/s/YrO0LvAA
constructor(){
super();
this.pre=this.pre.bind(this);
this.next=this.next.bind(this);
this.state ={
currentSlide :0
}
setInterval(()=>{
var current = this.state.currentSlide;
var next = current + 1;
if (next > this.props.stories.items.length - 1) {
next = 0;
}
this.setState({ currentSlide: next });
}, 5000);
}
One way to do this is to always have the future image (next image) be ready on the right side so you can transition it to the left, at the same time you transition the current one to the left. So they both move together.
In React this would mean that you would need to store the Index of both the current image and your next image, and each X seconds you need to move them both to the left (or the right depending on your action)
Here's a proof of concept:
https://codepen.io/nashio/pen/xLKepZ
const pics = [
'https://cdn.pixabay.com/photo/2017/06/19/07/12/water-lily-2418339__480.jpg',
'https://cdn.pixabay.com/photo/2017/07/18/18/24/dove-2516641__480.jpg',
'https://cdn.pixabay.com/photo/2017/07/14/17/44/frog-2504507__480.jpg',
'https://cdn.pixabay.com/photo/2016/09/04/13/08/bread-1643951__480.jpg',
];
class App extends React.Component {
constructor(props) {
super(props);
const idxStart = 0;
this.state = {
index: idxStart,
next: this.getNextIndex(idxStart),
move: false,
};
}
getNextIndex(idx) {
if (idx >= pics.length - 1) {
return 0;
}
return idx + 1;
}
setIndexes(idx) {
this.setState({
index: idx,
next: this.getNextIndex(idx)
});
}
componentDidMount() {
setInterval(() => {
// on
this.setState({
move: true
});
// off
setTimeout(() => {
this.setState({
move: false
});
this.setIndexes(this.getNextIndex(this.state.index));
}, 500); // same delay as in the css transition here
}, 2000); // next slide delay
}
render() {
const move = this.state.move ? 'move' : '';
if (this.state.move) {
}
return (
<div className="mask">
<div className="pic-wrapper">
<div className={`current pic ${move}`}>
{this.state.index}
<img src={pics[this.state.index]} alt="" />
</div>
<div className={`next pic ${move}`}>
{this.state.next}
<img src={pics[this.state.next]} alt="" />
</div>
</div>
</div>
);
}
}
React.render(<App />, document.getElementById('root'));
// CSS
.pic {
display: inline-block;
width: 100px;
height: 100px;
position: absolute;
img {
width: 100px;
height: 100px;
}
}
.current {
left: 100px;
}
.current.move {
left: 0;
transition: all .5s ease;
}
.next {
left: 200px;
}
.next.move {
left: 100px;
transition: all .5s ease;
}
.pic-wrapper {
background: lightgray;
left: -100px;
position: absolute;
}
.mask {
left: 50px;
overflow: hidden;
width: 100px;
height: 120px;
position: absolute;
}
EDIT: Updated the POC a bit to handle left and right navigation, see full thing HERE
This is by no means an elegant solution, but it does slide the image in from the left when it first appears: https://codesandbox.io/s/xWyEN9Yz
I think that the issue you're having is because you are only rendering the current story, but much of your code seemed to assume that there would be a rolling carousel of stories which you could animate along, like a reel.
With the rolling carousel approach it would be as simple as animating the left CSS property, and adjusting this based on the currently visible story. I.e. maintain some state in your component which is the current index, and then set the 'left' style property of your stories container to a multiple of that index. E.g:
const getLeftForIndex = index => ((index*325) + 'px');
<div className="ImageSlider" style={{ left: getLeftForIndex(currentStory) }}>
<Stories />
</div>

React: Set background image with props from another component

I hope I'm asking this question correctly (I'm still green with React).
I am writing a component that will set the background image (from a folder of jpgs) from props of another component. So let's say the component is "projects" then the background image would be set as "project.jpg" (and component "About" would have a background image of "about.jpg")
I've tried writing this and seems like I get close but not all the way there. Any React.js Gurus that could help me crack this code would have me singing praises of your screen name
My code
import React from 'react';
import styled from 'styled-components';
const pathToBgImages = require.context('../../../src/static', true);
const bgImages = [
'about.jpg',
'blog.jpg',
'contact.jpg',
'projects.jpg'
]
const getImages = () => bgImages.map(name => `<img src='${pathToBgImages(name, true)}'/>`);
const BgBackground = styled.div`
width: 100%;
&::after {
content: "";
background: url(${getImages});
background-size: cover;
opacity: 0.25;
height: 400px;
top: 0;
left: 0;
bottom: 0;
right: 0;
position: absolute;
z-index: -1;
}
`
class BgImage extends React.Component {
render() {
return (
<BgBackground />
);
}
}
export default BgImage;
I know there are some blanks in that component and not written correctly, I'm still trying to figure this out. Thank you in advance!

Categories