How to Optimize Elementor for Free Using Our Smush and Hummingbird Plugins

Elementor is no stranger to the WordPress community, considering it’s one of the best and most popular page builders out there. And to make it even better when it comes to optimization, you can enhance a ton with the help of our free plugins: Smush and Hummingbird.

WordPress sites built with Elementor sometimes have unnecessary slow page loading due to several factors, including large images, caching, CDN, and more.

Luckily, with Smush and Hummingbird, you can help alleviate any slowdowns and get your site up-and-running to its full potential.

We’ll have a look at some examples of what all Smush and Hummingbird can do to enhance your Elementor site. Plus, we’ll see some examples of them in action with two popular themes: Hello Elementor and Astra.

We’ll also include some general tips to ensure your Elementor site is up to speed.

In this article, we’ll be going over:

By the end of this article, you’ll have all the tools and know-how to get your Elementor site optimized for peak performance.

And we’ll show you exactly how we took an Elementor site from a PageSpeed score of 80/100, to 99/100 using just Smush and Hummingbird… so read on.

To get started, we’ll assume you have Elementor (and if you don’t — get it for free here). Also, if you haven’t already, download Smush and Hummingbird.

How to Optimize Elementor Using Smush

Like Elementor, Smush is no stranger to the WordPress community either. As an award-winning, 5-star, free image optimizer plugin, it has over a million active users to date.

Image of Smush.
Smush is pretty popular around these parts.

She collaborates well with Elementor to ensure that your image optimization is up to its highest standards and your site is speedy.

We’ll look at some of her awesome capabilities that work well with Elementor and any theme you’re using. They include:

Smushing All Your Elementor Images in Bulk

Depending on what Elementor site you build, you may have a ton of images already installed on your site.

Luckily, the Bulk Smush feature can take care of that with a click from Smush’s dashboard.

Button to bulk smash.
As you can see, 51 attachments in this example need re-smushing.

Once complete, Smush will let you know.

Message when bulk smush is complete.
All is smushed!

With Smush, you can also exclude specific images (e.g. thumbnails).

It’s a perfect way to optimize your images with Elementor quickly.

Find out more detailed information about bulk smushing here.

Compressing New Uploads Automatically

Any time you add new images to Elementor, it can compress them automatically with one-click.

In Smush’s dashboard, you can quickly turn on this option.

Automatic compress button.
Automatically compress new images with a click of the button.

That’s all it takes! All your new images will be compressed.

Super-Smushing for Double the Compression

Want to take image compression up a notch in Elementor? Super-Smush can offer up to twice the compression of regular smushing. It does this by stripping out every ounce of unneeded data.

The best thing is, it’s lossy compression, so there is some very minor drop in quality. It’s very minimal and unnoticeable to the human eye in most cases.

Like most things with Smush, you can do this in one-click from the admin.

Super-Smush is ready for all of your Elementor images.

Check it out and see if you notice a difference in image quality. We’re guessing you won’t.

Adding Larger Images with Smush’s Image Resizing

When you upload an image that is larger than 2560px in either height or width, it gets scaled down automatically by WordPress so it can generate a web-optimized maximum image size.

If you want to add larger images to your media library, you can override this with Smush’s Image Resizing.

Where you activate image resizing.
You’re one click away from image resizing.

Once you click, you can choose a new maximum image size.

Image resize sizes.
Enter in any new size you’d like.

You can also choose whether or not full-sized images are included in your Bulk Smush.

Where you smush original full size images.
Click these options to smush original full-size images and to store a copy of your small originals.

You’re in control of your full-size images with Image Resizing.

Converting Your PNGs to JPEGs

Depending on what Elementor page builder template you go with, you may find that using one of these two file types works better than the other. That being said, when it comes to speed, typically using JPEGS instead of PNGs should be a good enhancement.

Smush will check for you whether converting them to JPEGs will reduce file sizes and help speed up your Elementor site.

Where you auto-convert PNGs to JPEGs
Auto-converting a PNG to a JPEG is automatically done if it results in smaller file size.

Automatically converting your PNGs can’t get any easier.

Smushing From the Media Library

You can choose to select individual images for compression from the media library.

With Smush activated, you’ll see that there’s a new column called Smush. It will show you what images are optimized. If they’re not, you can click Smush and have it optimized in one-click.

All the smushing you want is at your fingertips.

Lazy Load Your Images in Elementor For a Speed Boost

You may have pages that contain a lot of images. Sometimes, displaying them all at once can put a strain on the server.

With Lazy Load, it stops offscreen images from loading until a user scrolls to them. This makes your page load faster, uses less bandwidth, and fixes the “defer offscreen images” recommendation via a Google PageSpeed test.

Where you activate lazy load.
You can choose which media types and refine lazy load to specific media outputs.

There’s a lot you can do with lazy load, including how you want the pre-loading images to appear, choices of animation, and duration of time before the image display comes into view.

You can read more about lazy loading in this article.

Take Advantage of Smush’s CDN

When you’re close to the server that’s providing your content, it’ll load quicker. That’s what a CDN (Content Delivery Network) does. It’s a series of servers located across the globe, so when a browser makes the HTTP request, the content is served from the nearest server to its location.

This feature is available with Smush Pro and well worth using. It boasts a 45-point CDN. Plus, it can automatically resize your images and also convert them to Google’s WebP format.

The smush CDN.
Clicking “Get Started” will launch the CDN.

A CDN will enhance any image-heavy Elementor site’s page speed by storing and serving copies of all of your JPG, PNG, and Gif images from the Smush edge servers.

For advanced options, Smush Pro’s CDN includes serving background images, automatic resizing, WebP conversion, and converting images from REST API requests in WordPress.

Get more detailed information Smush’s CDN here.

How to Optimize Elementor Using Hummingbird

Hummingbird and Elementor make a great team when it comes to making your website quicker. She helps optimize your site’s performance by fine-tuning controls with file compression, minify for CSS & JS, comment lazy loading, and much more.

Hummingbird image.
Hummingbird is here to help you speed up your Elementor site!

She’s a 4.5-star rated free plugin with over 100K active installations on wp.org (and counting!).

With her quick scan of your Elementor site, she’ll provide one-click fixes that help speed up your site with ease.

We’ll be going over some snippets of details of all she can do to improve your Elementor site and make it fly. Let’s take a look at:

Assessing Your Site with a Performance Test

Right off the bat, Hummingbird allows you to run a performance test to see how your Elementor site stands. You can get started in one-click by tapping Run Test.

Performance report.
You’re one click away from a performance report.

Once you run the test, Hummingbird will show you the Opportunities, Diagnostics, and Passed Audits. Plus, you can see the results for Desktop vs Mobile.

Simply click between desktop and mobile.

All of the detailed information is listed below. It starts with Opportunities. These are suggestions to improve your page load speed.

Opportunities in Hummingbird.
As you can see, the potential savings are indicated as well.

Next up is the Diagnostics. This provides additional information about how your page adheres to the best practices of web development.

The diagnostics area.
This test passed with flying colors.

And finally, you can see all of your Passed Audits. It includes everything with a score of 90 or more. Plus, you can click the dropdown for each category for more information.

Passed audits.
There were a lot of 100s on this audit.

The Performance Test is a great way to evaluate your Elementor site from the beginning so you can instantly see where improvements can be made.

Read more about performance testing in our article about optimizing Hummingbird.

Delivering Faster Pages and Content with Caching

If you haven’t heard, caching makes pages load faster. And what’s great about Hummingbird is she offers many types of caching, which works great when using Elementor with your WordPress theme, due to the various page options.

Her caching abilities include Page Caching, Browser Caching, Gravatar Caching, and RSS Caching. Additionally, if your site is hosted through us, your site speed is enhanced further with Object Cache, Redis Object Cache, and Static Server Cache.

Hummingbird dashboard.
Hummingbird is caching you up to speed quickly.

You’ll control every aspect of its caching functionality. This makes Hummingbird perfect for Elementor, considering each page builder is different and contains configurations that will vary.

You can see all the features and get a more detailed look at Hummingbird’s caching in our documentation.

Gzip Compression that Reducing File Sizes for Faster Serving

With Hummingbird’s Gzip compression, your Elementor pages can load quicker by compressing your site’s text-based resources into littler and more compact files that reach your users’ browsers faster.

The gzip compression area.
As you can see, Gzip is active for HTML, JavaScript, and CSS.

If you’re not hosting with us, you can change your Gzip compression to fit your server type (e.g. Apache) and follow instructions to get it activated.

Giving Your Page Speed a Boost with Asset Optimization

Hummingbird’s Asset Optimization area is where you can easily automate or manually configure advanced options when it comes to optimizing your Elementor site.

Asset optimization area.
As you can see, it shows my compression savings is 9.7%.

You can utilize Asset Optimization in two modes:

Automatic: Optimizing your assets and improving page load times based on Hummingbird’s automated options.

Manual: Manually set up each file yourself to achieve the exact setup you need for your Elementor site.

When Asset Optimization is activated, Hummingbird instantly scans your WordPress site’s assets to point out those which could be optimized for performance.

There are a lot of tweaks and adjustments that can be made with Hummingbird’s Asset Optimization. Be sure to read our documentation to see how fine-tuned you can make your site for maximum optimization.

Examples of Enhancing Two Top WordPress Themes — Hello Elementor & Astra — with Smush, Hummingbird, and Elementor

Now that we’ve looked at glimpses of what Smush and Hummingbird can do to enhance the performance of your Elementor site let’s take a closer look at specific examples using two popular Elementor themes: Hello Elementor and Astra.

As mentioned before, we will be using the free version of Elementor, so there are no costs involved in this initial setup (again, Smush and Hummingbird are free, too).

This is a new site with no other pages, plugins, or anything installed at first except for Elementor and the theme.

We’ll walk through what it looks like adding all of these features and the difference they make. To break it all down, this is how we’ll do it:

  • Set up a WordPress site using Elementor page builder content and theme
  • Run speed tests with Google PageSpeed Insights and GTmetrix
  • Activate Hummingbird and Smush and set up recommendations
  • Run another speed test
  • Activate Hosting Features (e.g. fast CGI)
  • Run a final speed test

Keep in mind, EVERY site is going to be different. Location, amount of images, your host, and other factors will make a difference. However, this should give you a general idea of what can be done, and then you can tweak it accordingly on your Elementor-based WordPress site.

Hello Elementor

To kick things off, we’ll start with the Hello Elementor theme. This free theme is a favorite amongst Elementor users and was created by Elementor, so you know it’s good.

Hello Elementor theme.
Hello Elementor is a popular, high-rated theme for Elementor users.

We’ve set up a site using this theme and have activated the Landing Page – Hotel page from Elementor’s library. This theme features a handful of images and text, making for a good example.

Elementor page building themes.
You can activate this landing page directly from the library in your WordPress dashboard.

I’ve also made this page my homepage. You can ensure this is done by going to Appearance > Customize > Homepage Settings and selecting this landing page.

Now that this is installed, we’re ready to go! Let’s do a quick speed test on this site. We’ll start with Google PageSpeed Insights.

When entering the URL, here’s our Google PageSpeed Insights score:

Google page speed test.
As you can see, it’s in the middle range.

Now let’s check it out with GTmetrix.

I’ll note that I’m using GTmetrix’s default settings for server location (Vancouver, Canada) and browser (Chrome). This will not be changed throughout all of these examples.

GTmetrix score.
‘E’? Yikes.

Let’s go ahead and activate Smush and Hummingbird and see what we come up with.

I’ll start with running a Performance Test with Hummingbird and check out her recommendations.

Once doing that, she mentions we have a score of 56/100 and offers several opportunities, such as preload key requests, eliminating render-blocking resources, reducing initial server response time, and removing unused CSS.

Also, her diagnostics show that we should ensure text remains visible during webfont load.

It also shows that there were 15 passed audits (yippee!).

Hummingbird's performance test.
Hummingbird’s got a few recommendations for us.

I’ll go ahead and handle all of the suggestions possible. The dropdown in Hummingbird mentions exactly how to take care of recommendations.

For example, here, Hummingbird shows how to remove unused CSS.

Removing unnecessary CSS.
How to fix is written out for you.

I also activated Hummingbird’s page caching to help with initial server response time, optimized my assets, and combined & compressed assets.

Next, I’ll go ahead and activate Smush.

Right away, Smush points out that I have 61 images and attachments that need smushing, which can help save me an estimated 815.5 kb. I’ll Bulk Smush these in one-click.

The bulk smushing button.
Bulk smashing is a click away.

After a few moments, here are the results:

The new numbers from bulk smushing.
Nice!

As you can see, there are 872.5 kb/9.3% total savings.

Now, let’s go ahead and recheck our page speed.

First, here’s Google PageSpeed Insights:

Google pagespeed insights score.
99 is okay in our books.

And here’s GTmetrix:

GTmetrix score of 99.
Pretty good here, too!

Even though I’m happy with this score and can’t get much better than this, I’m going to crank things up even further.

I’ll go ahead and turn on our CDN in Hummingbird’s Asset Optimization section. Whether you’re hosting with us or another company, you can turn this on for added optimization.

One-click to turn on the CDN.

For more on CDNs and why it’s beneficial for site speed, be sure to check out this article.

Also, to ensure I have the best speed possible, I’m going to go under Tools in The Hub and flip on Static Server Cache to use FastCGI.

Where to activate fast CGI.
We’ll change this to “on” and be in business.

And now, with all of these tweaks in place, my Elementor site using the Hello Elementor theme is optimized for speed!

Let’s now take a look at optimizing the popular Astra theme with Elementor, Hummingbird, and Smush.

Astra

Astra is another extremely popular theme that works well with Elementor. With over 1 million+ active downloads and a rock-solid 5-star review on wp.org, you know it’s a good choice for many Elementor users.

The Astra theme.
Astra is extremely popular — especially with Elementor.

Like with Hello Elementor, we’ve set up a site using this theme and have activated the Landing Page – Hotel page from Elementor’s library.

Also, like with the previous them, I’ve made the Hotel page my homepage.

Let’s go ahead and perform a Google PageSpeed Insight speed test and see how we do.

Google PageSpeed Insights score of 66.
Ouch…

I ran this test several times and kept getting the same results. It’s a poor score, indeed.

Let’s see how we do with GTmetrix.

GTmetrix score.
This is better.

Right away, after running it multiple times, it got an “A” right off the bat with a 97% performance score and 95% structure score. Pretty good score! However, as I mentioned previously, this will vary depending on the number of images, location, host, and other factors you have in place.

Also, in case you didn’t know, there are reasons for such drastic differences in scores between Google and GTmetrix due to how they both test. For more information about how they vary when it comes to testing sites, check out this article on GTmetrix’s site.

Now, let’s see if we can improve the score on Google and boost the GTmetrix to an A+ by activating Hummingbird and Smush.

We’ll start with Hummingbird and run a performance test.

Hummingbird performance test.
Hummingbird has this site coming in with 80/100 as a performance score.

As you can see, there are two opportunities for improvement and 17 passed audits.

To improve, Hummingbird is recommending eliminating render-blocking resources and removing unused CSS.

Hummingbird opportunities.
You can see the potential savings with each recommendation.

We’ll make Hummingbird’s recommendations and move on to Smush.

With Smush, right away, she tells us there are 15 items to bulk smush, so we’ll go ahead and do that.

Bulk smushing button.
Bulk smushing is as simple as ever (and fun!).

Once bulk smushing is over, we’ll run another Google PageSpeed test (since that’s the one that’s fairing so poorly) and see what we’re looking like.

Google PageSpeed Insight score of 72.
The newest test isn’t that great still.

Since it didn’t go up that much, let’s try a few more tweaks.

Smush recommends things for you that can help improve your score. In this example, they include:

  • Enabling Super-Smush for advanced lossy compression
  • Ensuring images are the rights size for our theme (Astra)
  • Enabling resize full-size images to scale big images down to a reasonable size
Smush recommendations.
Here are the recommendations for Smush.

Now that I made the Smush recommendations let’s check it out again.

Google pagespeed insight score of 86.
A higher score! Yippee!

As you can see, it went up a bit. We’re not quite at 90-100; however, with some tweaks like turning on Hummingbird’s CDN and also FastCGI, we should be able to hit that high mark.

Before that, let’s see how we’re scoring with GTmetrix.

Gtmetrix report showing a A rating.
Still have a nice “A” going.

As you can see, this hasn’t changed much. It was already a good score to start with, and minor fluctuations will occur. It’s essentially the same (which is good).

Finally, let’s put this into high gear.

When turning on the CDN and FastCGI (see an example of how to do so in the Hello Elementor example) our score instantly improved with Google.

PageSpeed insight score of 90.
A 90 isn’t too shabby.

We can probably make some fine-tuning in Hummingbird to eventually get that score even higher; however, it’s at a pretty good place. With all of the tools at our disposal, it should stay this way, too — and not drop.

General Tips for Speeding Up Elementor

Along with using Smush and Hummingbird, when it comes down to speeding up Elementor, there are some basic tips you can follow.

Things to keep in mind include:

  • Choosing a performance-optimized host like ours here at WPMU DEV. We even offer templates that fit Elementor perfectly for total optimization (e.g. our Charity template).
  • Using a lightweight theme.
  • Ensuring your images are optimized with the help of Smush.
  • Getting rid of slow, outdated, or unused plugins.
  • Minifying code and concatenate files
  • Using page caching to generate static HTML versions of your content with the help of Hummingbird.

Keep these tips in mind to ensure your WordPress Elementor site is optimized to its full potential.

Elementor Optimization Made Easy

After all that we covered in this article, your Elementor WordPress site should be in great shape when it comes to performance, optimization, hosting, and all its capabilities. And as you can see, it’s easy to do with the help of Hummingbird and Smush.

Every site will have its individual tweaks and adjustments that need to be made, but it’s just a matter of fine-tuning it to perfection. Add in some good hosting, and you’ll be in a position to having an amazing Elementor site.

New to Java and would appreciate the help.

New to Java and would appreciate the help with the assignment: Write a program to collect exam grades from keyboard until an invalid grade (grade less than 0 or bigger than 100) inputted. Find how many grades were entered, the corresponding average grade, how many grades above 90, and how many grades lower than 60
-I am able to input the first two number and then recieve an error.
Could someone please simply explain why this does not work properly? Thank you!

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
int grades;
int avg=0;
int totalCount=0;
int agrades=0;
int fgrades=0;
Scanner input = new Scanner(System.in);

System.out.println("Enter Grades: ");
grades = input.nextInt();
boolean numbers = true;
while (numbers){
    grades = input.nextInt();
    if (grades < 0 || grades > 100)
        numbers = false;

    avg = grades + grades/totalCount;
    totalCount++;
    if (grades >= 90)
        agrades++;
    if (60 >= grades)
            fgrades++;
    System.out.println("Enter Grades: ");
    grades = input.nextInt();
}
System.out.println("Amount of Grade: " + totalCount);
System.out.println("Averaage Grade: " + avg);
System.out.println("Number of grades above 90: " + agrades);
System.out.println("Number of grades below 60: " + fgrades);
    }
}

Hiding Content Responsibly

We’ve covered the idea of hiding things in CSS many times here, the most recent post being Marko Ilic’s “Comparing Various Ways to Hide Things in CSS” which did a nice job of comparing different techniques which you’d use in different situations. Hugo “Kitty” Giraudel has done something similar in “Hiding Content Responsibly” which looks at 10 methods—and even those, you could say, aren’t totally comprehensive.

Does this mean CSS is messy and incomprehensible? Nah. I feel like all the methods are logical, have good use cases, and result in good outcomes. Allow me to do a have a pretend conversation walking through my thought process here.

I need to hide this thing completely. For everyone.

No problem, use display: none;.

I need to hide this thing, but only hide it for screen readers, not visually. (For example, an icon that has no additional meaning for screen readers, as there is an accessible label nearby.)

No problem, that’s what the aria-hidden attribute is for.

I need to hide this thing, but only visually, not for screen readers. (For example, the contents of non-active tabs.)

No problem, use a .sr-only class. That leaves it accessible but hides it visually until you remove that class.

Oops, I actually want to hide this thing visually, but I still want it to take up physical space, not collapse. (For example, say a button has a loading spinner icon that is only needed when performing an action. The size of the button should factor in the size of that icon all the time, not just when the spinner is visible. That way, there’s no layout shifting when that icon comes and goes.)

No problem, use transform: scale(0) which will visually collapse it, but the original space will remain, and will leave it accessible to screen readers.

Oh nice, I could transition the transform, too, I suppose. But actually, that transition doesn’t fit my site well. I just want something I can fade out and fade in.

The opacity property is transitional, so transition that between 0 and 1 for fades. The good news is that visibility is also transitional. When fading out, use visibility: hidden, and when fading in, use visibility: visible to hide and unhide the thing from screen readers.


That’s not entirely comprehensive, but I find that covers 95% of hiding cases.


The post Hiding Content Responsibly appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

WordPress 5.7 Lets Administrators Send Password Reset Links

It’s that time in the release cycle when all the dev notes are rolling out ahead of the next major update. These notes include technical summaries of all the goodies coming in the next release. If you haven’t been paying close attention, there are always a few happy surprises in there that pop up as conclusions to tickets that contributors have been working on for years.

The new password reset feature coming in WordPress 5.7 allows administrators to manually send a password reset link to users, resolving a five-year old ticket. Instead of having to instruct a user about where to go to click on the lost password link and follow the steps, this new feature lets administrators push a button in the admin to send the link. If you have ever had to support clients or a community of users who may not be very technically inclined, this new password reset feature will save lots of time in helping users regain access to their accounts.

The “Send password reset” link is available in several places. Administrators can find the link on the Users screen, as well as in the bulk actions dropdown menu.

It is also available on the individual user screen with a button and a note clarifying that this action will not change the user’s password or force the user to change it.

The password reset email notification includes the site name, username, a password reset link, and the IP address where the request originated:

This password reset request originated from the IP address [IPADDRESS].

There is an open discussion on the original ticket regarding whether this email notification should include the administrator’s IP address.

“The IP address (while fraught with privacy concerns) is the only thing validating that this email came from the website and is not a phishing email,” contributor Gabriel Mariani said. “Unless there is a better way to validate the authenticity of the email I’d say it would be worthwhile to keep it.”

Others see the IP address as useful only if a user is attempting to verify that it is their own IP address or collecting the information to prevent a phishing attack. Giving out the administrator’s IP address doesn’t seem pertinent to either of those concerns.

“I could use my phone to send a reset, and I would have no idea what my IP was,” Mika Epstein said. “And that can easily be faked. Omitting the IP actually reduces the data being sent out that could be used by bad-actors.

“I think it’s more likely we’d have a savvy bad actor than end users who would need to ask for a password reset but also know what a valid IP is and how to ask about it.”

This part of the email text may be iterated on in subsequent patches or future releases of WordPress. Check out the dev note for more discussion on this feature, along with information about further customizing the notification email.

FSE Outreach Round #2: Building a Custom Homepage With Gutenberg’s Site Editor

Anne McCarthy announced the second round of testing for the Full Site Editing (FSE) Outreach program. The call for testing asks that users build a homepage from the Gutenberg plugin’s site editor. Feedback is open until March 5.

The first round of testing began in December 2020 and ended last month. Testers were able to identify several pain points with template-editing mode from the block editor. The program created actionable items that Gutenberg developers could work to improve.

This second round is similar. However, testing covers a much larger and more complex area. Users will be leaving the familiar block editor and moving to the site editor, which is still months away from being a viable product.

McCarthy listed a 22-step process for building out a homepage. While I followed it, for the most part, I got bored before finishing. This is one of the reasons I make for a poor test subject. I like to explore and see what is possible on my own. If I have an idea, I want to attempt its execution. I primarily stuck to the overall script, even if it was a bit out of order.

Eventually, I created a custom homepage for a restaurant called The Grilled Cheese — I would definitely open this restaurant in the real world if I ever leave the WordPress community.

Custom-designed homepage via Gutenberg's site editor for a fictitious restaurant.
Custom restaurant homepage with sidebar.

It is reasonably simple. All told, it took me around two hours of playing around with various concepts before arriving at this stage. To build something I would be happy with would have taken a few more hours.

Overall, I felt limited in laying out my ideal homepage. Each step was an uphill battle against the tools. I could have built this in less than half the time with HTML and CSS. I could do the same and more with other modern page builder plugins for WordPress.

Before diving into the results of my test, I have some brutal honesty. TT1 Blocks, which is the theme used for FSE Outreach testing, is not up to snuff. The theme does not reliably handle the multitude of possibilities the site editor sets in the hands of end-users. This entire experience could be made smoother with a better theme. However, the choices are limited, and I am not sure if there is a better block-based theme to work with at this point.

There were so many inconsistencies between the site editor and the front end that there is little point in listing them all. Spacing was grossly off. I generally see that as a theme issue. I spent much of my time in trial-and-error mode, making an adjustment in the editor and refreshing to see the front-end result. Rinse. Repeat.

Identifying Pain Points

While this post is critical of the site editor, it does not mean the experience was altogether poor. Seeing the improvement every week gives me hope that WordPress will have a site editor that rivals anything on the market. Eventually. However, my goal here is to provide real feedback that the team can use.

Outside of the general spacing issues mentioned earlier, I identified several stumbling blocks while building a custom homepage.

Maximum Widths

When designing a full-site page via the site editor, one problem stood out more than most. WordPress lacks a well-rounded “max-width” system. As a user, I was left with few choices in setting the width of the content area of my homepage. Currently, theme authors can set custom content, wide, and full widths. However, this system is horribly limiting. There is not much theme authors can do with this, and this problem directly limits what users can do in both the block and site editors.

I have previously written about the need for a design framework, one that is customizable by theme authors. Tailwind CSS has a max-width system that offers a boatload of flexibility. WordPress needs to start borrowing ideas from these modern design frameworks.

Add Block Icon

Getting the “Add Block” icon to appear when hovering in between elements in the default content area was rough. I had to position my mouse in a perfect position for it to appear. It was an exercise in frustration where even the slightest movement caused the icon to once again disappear.

Hovering over the 'Add Block' icon in the site editor.
Locating the ‘Add Block’ icon.

Switching to Top Toolbar mode made this far easier. I am assuming the default block toolbar was hiding it to some degree. The problem with switching to this mode is that my toolbar-choice was not saved. Each time I returned to the site editor, I had to enable it once again.

Query Block

The most frustrating aspect of listing posts on a custom homepage was setting a limit. I wanted to set the number to three. However, the Query block has no option for doing this. Eventually, I created a faux limit using the category filter, choosing one that had just a couple of posts.

Update: It is possible to set a limit as noted by Nick in the comments. There is a “settings” icon in the toolbar for setting the number of posts per page, an offset, and max number of pages. I am unsure why these particular query settings are separate from the others in the sidebar. It makes more sense for them to be grouped together.

Using the Query block in the WordPress site editor.
Limiting posts by using the Query block’s category filter.

Another confusing aspect of the Query block is the keyword filter. As far as I am aware, WordPress has never used the “keyword” terminology. Outside of SEO plugins, there does not seem to be any context for what this filter does. I am guessing it works like a search keyword.

Global Styles for All Blocks

When switching over to the Global Styles panel, I noticed that some blocks were missing when applying styles on the block level. In particular, I wanted to adjust styles for the Latest Comments block.

I suppose that only blocks with typography, colors, and other design-related options appear in the list. This will likely confuse end-users when the site editor lands in WordPress. All blocks should have style options that users can customize.

No Full-Width Columns

For the content of my homepage, I attempted to create a full-width Columns block. However, the two individual columns were limited in size despite taking up 66.67% and 33.33%, respectively.

Inspecting the output of full-width columns from the site editor.
Full-width columns not spanning the full area.

This seems like it is a theme issue. I would also argue that this is one of those times where having more direct control over the max-width would have helped. I really wanted something that was between the theme’s full and wide widths.

Featured Images

There is no way to set the size of the image output by the Post Featured Image block. The only way to get a uniform size at the moment is to pre-crop the images before uploading them to WordPress.

There is no reason this should not essentially be a variation of the Image block. The only thing featured images need that is different is the option to link to the post.

vb.net – how can I remove first characters of a string?

hi,

I got a string and it look like : "test : this is a test. for me."
How can I remove the first charakters? At this example I want to remove the characters "test : ". How can I do this?

I know how to delete from the end, but I realy need it from the beginning :

Dim s As String = Label1.Text
TextBox1.Text = s.Substring(s.Length-9)

React Component Tests for Humans

React component tests should be interesting, straightforward, and easy for a human to build and maintain.

Yet, the current state of the testing library ecosystem is not sufficient to motivate developers to write consistent JavaScript tests for React components. Testing React components—and the DOM in general—often require some kind of higher-level wrapper around popular testing frameworks like Jest or Mocha.

Here’s the problem

Writing component tests with the tools available today is boring, and even when you get to writing them, it takes lots of hassle. Expressing test logic following a jQuery-like style (chaining) is confusing. It doesn’t jive with how React components are usually built.

The Enzyme code below is readable, but a bit too bulky because it uses too many words to express something that is ultimately simple markup.

expect(screen.find(".view").hasClass("technologies")).to.equal(true);
expect(screen.find("h3").text()).toEqual("Technologies:");
expect(screen.find("ul").children()).to.have.lengthOf(4);
expect(screen.contains([
  <li>JavaScript</li>,
  <li>ReactJs</li>,
  <li>NodeJs</li>,
  <li>Webpack</li>
])).to.equal(true);
expect(screen.find("button").text()).toEqual("Back");
expect(screen.find("button").hasClass("small")).to.equal(true);

The DOM representation is just this:

<div className="view technologies">
  <h3>Technologies:</h3>
  <ul>
    <li>JavaScript</li>
    <li>ReactJs</li>
    <li>NodeJs</li>
    <li>Webpack</li>
  </ul>
  <button className="small">Back</button>
</div>

What if you need to test heavier components? While the syntax is still bearable, it doesn’t help your brain grasp the structure and logic. Reading and writing several tests like this is bound to wear you out—it certainly wears me out. That’s because React components follow certain principles to generate HTML code at the end. Tests that express the same principles, on the other hand, are not straightforward. Simply using JavaScript chaining won’t help in the long run.

There are two main issues with testing in React:

  • How to even approach writing tests specifically for components
  • How to avoid all the unnecessary noise

Let’s further expand those before jumping into the real examples.

Approaching React component tests

A simple React component may look like this:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

This is a function that accepts a props object and returns a DOM node using the JSX syntax.

Since a component can be represented by a function, it is all about testing functions. We need to account for arguments and how they influence the returned result. Applying that logic to React components, the focus in the tests should be on setting up props and testing for the DOM rendered in the UI. Since user actions like mouseover, click, typing, etc. may also lead to UI changes, you will need to find a way to programmatically trigger those too.

Hiding the unnecessary noise in tests

Tests require a certain level of readability achieved by both slimming the wording down and following a certain pattern to describe each scenario.

Component tests flow through three phases:

  1. Preparation (setup): The component props are prepared.
  2. Render (action): The component needs to render its DOM to the UI before either triggering any actions on it or testing for certain texts and attributes. That’s when actions can be programmatically triggered.
  3. Validation (verify): The expectations are set, verifying certain side effects over the component markup.

Here is an example:

it("should click a large button", () => {
  // 1️⃣ Preparation
  // Prepare component props
  props.size = "large";

  // 2️⃣ Render
  // Render the Button's DOM and click on it
  const component = mount(<Button {...props}>Send</Button>);
  simulate(component, { type: "click" });

  // 3️⃣ Validation
  // Verify a .clicked class is added 
  expect(component, "to have class", "clicked");
});

For simpler tests, the phases can merge:

it("should render with a custom text", () => {
  // Mixing up all three phases into a single expect() call
  expect(
    // 1️⃣ Preparation
    <Button>Send</Button>, 
    // 2️⃣ Render
    "when mounted",
    // 3️⃣ Validation
    "to have text", 
    "Send"
  );
});

Writing component tests today

Those two examples above look logical but are anything but trivial. Most of the testing tools do not provide such a level of abstraction, so we have to handle it ourselves. Perhaps the code below looks more familiar.

it("should display the technologies view", () => {
  const container = document.createElement("div");
  document.body.appendChild(container);
  
  act(() => {
    ReactDOM.render(<ProfileCard {...props} />, container);
  });
  
  const button = container.querySelector("button");
  
  act(() => {
    button.dispatchEvent(new window.MouseEvent("click", { bubbles: true }));
  });
  
  const details = container.querySelector(".details");
  
  expect(details.classList.contains("technologies")).toBe(true);
  expect(details.querySelector("h3").textContent, "to be", "Technologies");
  expect(details.querySelector("button").textContent, "to be", "View Bio");
});

Compare that with the same test, only with an added layer of abstraction:

it("should display the technologies view", () => {
  const component = mount(<ProfileCard {...props} />);

  simulate(component, {
    type: "click",
    target: "button",
  });

  expect(
    component,
    "queried for first",
    ".details",
    "to exhaustively satisfy",
    <div className="details technologies">
      <h3>Technologies</h3>
      <div>
        <button>View Bio</button>
      </div>
    </div>
  );
});

It does look much better. Less code, obvious flow, and more DOM instead of JavaScript. This is not a fiction test, but something you can achieve with UnexpectedJS today.

The following section is a deep dive into testing React components without getting too deep into UnexpectedJS. Its documentation more than does the job. Instead, we’ll focus on usage, examples, and possibilities.

Writing React Tests with UnexpectedJS

UnexpectedJS is an extensible assertion toolkit compatible with all test frameworks. It can be extended with plugins, and some of those plugins are used in the test project below. Probably the best thing about this library is the handy syntax it provides to describe component test cases in React.

The example: A Profile Card component

The subject of the tests is a Profile card component.

A card component where the persons name, photo, and number of posts are displayed to the left in a single column with a light red background, and the bio is displayed on the right in paragraph form with a title against a white background.

And here is the full component code of ProfileCard.js:

// ProfileCard.js
export default function ProfileCard({
  data: {
    name,
    posts,
    isOnline = false,
    bio = "",
    location = "",
    technologies = [],
    creationDate,
    onViewChange,
  },
}) {
  const [isBioVisible, setIsBioVisible] = useState(true);

  const handleBioVisibility = () => {
    setIsBioVisible(!isBioVisible);
    if (typeof onViewChange === "function") {
      onViewChange(!isBioVisible);
    }
  };

  return (
    <div className="ProfileCard">
      <div className="avatar">
        <h2>{name}</h2>
        <i className="photo" />
        <span>{posts} posts</span>
        <i className={`status ${isOnline ? "online" : "offline"}`} />
      </div>
      <div className={`details ${isBioVisible ? "bio" : "technologies"}`}>
        {isBioVisible ? (
          <>
            <h3>Bio</h3>
            <p>{bio !== "" ? bio : "No bio provided yet"}</p>
            <div>
              <button onClick={handleBioVisibility}>View Skills</button>
              <p className="joined">Joined: {creationDate}</p>
            </div>
          </>
        ) : (
          <>
            <h3>Technologies</h3>
            {technologies.length > 0 && (
              <ul>
                {technologies.map((item, index) => (
                  <li key={index}>{item}</li>
                ))}
              </ul>
            )}
            <div>
              <button onClick={handleBioVisibility}>View Bio</button>
              {!!location && <p className="location">Location: {location}</p>}
            </div>
          </>
        )}
      </div>
    </div>
  );
}

We will work with the component’s desktop version. You can read more about device-driven code split in React but note that testing mobile components is still pretty straightforward.

Setting up the example project

Not all tests are covered in this article, but we will certainly look at the most interesting ones. If you want to follow along, view this component in the browser, or check all its tests, go ahead and clone the GitHub repo.

## 1. Clone the project:
git clone git@github.com:moubi/profile-card.git

## 2. Navigate to the project folder:
cd profile-card

## 3. Install the dependencies:
yarn

## 4. Start and view the component in the browser:
yarn start

## 5. Run the tests:
yarn test

Here’s how the <ProfileCard /> component and UnexpectedJS tests are structured once the project has spun up:

/src
  └── /components
      ├── /ProfileCard
      |   ├── ProfileCard.js
      |   ├── ProfileCard.scss
      |   └── ProfileCard.test.js
      └── /test-utils
           └── unexpected-react.js

Component tests

Let’s take a look at some of the component tests. These are located in src/components/ProfileCard/ProfileCard.test.js. Note how each test is organized by the three phases we covered earlier.

  1. Setting up required component props for each test.
beforeEach(() => {
  props = {
    data: {
      name: "Justin Case",
      posts: 45,
      creationDate: "01.01.2021",
    },
  };
});

Before each test, a props object with the required <ProfileCard /> props is composed, where props.data contains the minimum info for the component to render.

  1. Render with a default set of props.

This test checks the whole DOM produced by the component when passing name, posts, and creationDate fields.

Here’s what the result produces in the UI:

And here’s the test case for it:

it("should render default", () => {
  // "to exhaustively satisfy" ensures all classes/attributes are also matching
  expect(
    <ProfileCard {...props} />,
    "when mounted",
    "to exhaustively satisfy",
    <div className="ProfileCard">
      <div className="avatar">
        <h2>Justin Case</h2>
        <i className="photo" />
        <span>45{" posts"}</span>
        <i className="status offline" />
      </div>
      <div className="details bio">
        <h3>Bio</h3>
        <p>No bio provided yet</p>
        <div>
          <button>View Skills</button>
          <p className="joined">{"Joined: "}01.01.2021</p>
        </div>
      </div>
    </div>
  );
});
  1. Render with status online.

Now we check if the profile renders with the “online” status icon.

And the test case for that:

it("should display online icon", () => {
  // Set the isOnline prop
  props.data.isOnline = true;

  // The minimum to test for is the presence of the .online class
  expect(
    <ProfileCard {...props} />,
    "when mounted",
    "queried for first",
    ".status",
    "to have class",
    "online"
  );
});
  1. Render with bio text.

<ProfileCard /> accepts any arbitrary string for its bio.

So, let’s write a test case for that:

it("should display online icon", () => {
  // Set the isOnline prop
  props.data.isOnline = true;

  // The minimum to test for is the presence of the .online class
  expect(
    <ProfileCard {...props} />,
    "when mounted",
    "queried for first",
    ".status",
    "to have class",
    "online"
  );
});
  1. Render “Technologies” view with an empty list.

Clicking on the “View Skills” link should switch to a list of technologies for this user. If no data is passed, then the list should be empty.

Here’s that test case:

it("should display the technologies view", () => {
  // Mount <ProfileCard /> and obtain a ref
  const component = mount(<ProfileCard {...props} />);

  // Simulate a click on the button element ("View Skills" link)
  simulate(component, {
    type: "click",
    target: "button",
  });

  // Check if the .details element contains the technologies view
  expect(
    component,
    "queried for first",
    ".details",
    "to exhaustively satisfy",
    <div className="details technologies">
      <h3>Technologies</h3>
      <div>
        <button>View Bio</button>
      </div>
    </div>
  );
});
  1. Render a list of technologies.

If a list of technologies is passed, it will display in the UI when clicking on the “View Skills” link.

Yep, another test case:

it("should display list of technologies", () => {
  // Set the list of technologies
  props.data.technologies = ["JavaScript", "React", "NodeJs"];
 
  // Mount ProfileCard and obtain a ref
  const component = mount(<ProfileCard {...props} />);

  // Simulate a click on the button element ("View Skills" link)
  simulate(component, {
    type: "click",
    target: "button",
  });

  // Check if the list of technologies is present and matches prop values
  expect(
    component,
    "queried for first",
    ".technologies ul",
    "to exhaustively satisfy",
    <ul>
      <li>JavaScript</li>
      <li>React</li>
      <li>NodeJs</li>
    </ul>
  );
});
  1. Render a user location.

That information should render in the DOM only if it was provided as a prop.

The test case:

it("should display location", () => {
  // Set the location 
  props.data.location = "Copenhagen, Denmark";

  // Mount <ProfileCard /> and obtain a ref
  const component = mount(<ProfileCard {...props} />);
  
  // Simulate a click on the button element ("View Skills" link)
  // Location render only as part of the Technologies view
  simulate(component, {
    type: "click",
    target: "button",
  });

  // Check if the location string matches the prop value
  expect(
    component,
    "queried for first",
    ".location",
    "to have text",
    "Location: Copenhagen, Denmark"
  );
});
  1. Calling a callback when switching views.

This test does not compare DOM nodes but does check if a function prop passed to <ProfileCard /> is executed with the correct argument when switching between the Bio and Technologies views.

it("should call onViewChange prop", () => {
  // Create a function stub (dummy)
  props.data.onViewChange = sinon.stub();
  
  // Mount ProfileCard and obtain a ref
  const component = mount(<ProfileCard {...props} />);

  // Simulate a click on the button element ("View Skills" link)
  simulate(component, {
    type: "click",
    target: "button",
  });

  // Check if the stub function prop is called with false value for isBioVisible
  // isBioVisible is part of the component's local state
  expect(
    props.data.onViewChange,
    "to have a call exhaustively satisfying",
    [false]
  );
});

Running all the tests

Now, all of the tests for <ProfileCard /> can be executed with a simple command:

yarn test

Notice that tests are grouped. There are two independent tests and two groups of tests for each of the <ProfileCard /> views—bio and technologies. Grouping makes test suites easier to follow and is a nice way to organize logically-related UI units.

Some final words

Again, this is meant to be a fairly simple example of how to approach React component tests. The essence is to look at components as simple functions that accept props and return a DOM. From that point on, choosing a testing library should be based on the usefulness of the tools it provides for handling component renders and DOM comparisons. UnexpectedJS happens to be very good at that in my experience.

What should be your next steps? Look at the GitHub project and give it a try if you haven’t already! Check all the tests in ProfileCard.test.js and perhaps try to write a few of your own. You can also look at src/test-utils/unexpected-react.js which is a simple helper function exporting features from the third-party testing libraries.

And lastly, here are a few additional resources I’d suggest checking out to dig even deeper into React component testing:


The post React Component Tests for Humans appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

What’s the Backup Plan for Your WordPress Site?

Of all the reasons we love and use Jetpack for CSS-Tricks—a poster child WordPress site—is that we can sleep easy at night knowing we have real-time backups running with Jetpack Backup. That way, no matter what, everything that makes this site tick, from all the template files to every single word we’ve ever typed, is always a click away from being restored if we need it for any reason at all.

There’s really no question whether or not you should be backing up your WordPress site. You absolutely should. It’s sort of like being prepared for an earthquake: you know it could happen at any time, so you want to make sure you’ve got all the tooling in place to keep things safe, not if, but when it happens.

What’s your backup plan? For us, it’s logging into WordPress.com, locating which backup to use, and clicking a button to restore things to that point in time. That’s all the files of course, like WordPress itself, the theme, and plugins, but also the entire database and all the media files.

Another reason we love Jetpack Backup? It provides a complete activity log of all the changes that happen on the site. It’s one thing to have your site backed up and be able to restore it. It’s another to know what caused the issue in the first place.

Jetpack Backup offers two plans one for daily backups and the other for real-time backups. We’ve got real-time running around here and that’s a great option for large sites that are updated often, like e-commerce. Most sites can probably get away with daily backups instead.

That leads to another wonderful thing: Jetpack Backup is sold à la carte. That means you can just get backups if that’s all you want from Jetpack. And, hey, if you find yourself needing more from Jetpack, like all the stuff we use it for here, then that rich feature set is just a couple of clicks away.

Not sure what your WordPress backup plan is? You really ought to check out Jetpack Backup. It works extremely well for us and we can’t recommend it enough.


True Story

While working on a bit of content for @css across team members, I noticed some of what I had written had disappeared. It got saved over by accident. I forgot to turn on “Revisions” for this Custom Post Type (it was a newsletter, which we write in WordPress).

It was tempting to be like, “Oh well, I’m a dummy, I’ll just have to remember and re-write it.” But no! I have Jetpack real-time backups on this site. I was able to find the exact moment I made my changes and download a copy of the site at that moment.

I didn’t need to restore the site to that point, just what I had written. So, I loaded up the wp_posts table from the SQL dump in that backup, plucked out my writing, and put it back in place.

And of course, I enabled revisions for that Custom Post Type so it won’t happen again.

Not only is that a true story, but this is a Jetpack double-whammy, because I “unrolled” that Twitter thread right here in WordPress via a Jetpack feature.


The post What’s the Backup Plan for Your WordPress Site? appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Ooma Review

Communication is vital when it comes to your business. That means you need a VoIP phone system that serves you well.

Ooma offers plenty of basic domestic and international calling features, plus more advanced ones to help your business thrive, like video calling and analytics.

But the system isn’t perfect. That’s why we’re here to compare it to some other VoIP systems to help you make your final decision on whether Ooma is the one for you. We consider Ooma a solid option for most businesses. Check out what it has to offer and see if yours is one of them.

Ooma Pros and Cons

Pros

  • Great call quality
  • User-friendly
  • 24/7 customer support
  • Desktop and mobile apps
  • Advanced calling features with the basic package

Cons

  • High initial cost
  • Pricey international calling
  • No video conferencing with Office package
Compare The Best VoIP Phone Services
We reviewed dozens of VoIP phone service providers and narrowed them down to the best options.
See Top Picks

How Ooma Compares to Top VoIP Phone Services

Ooma consistently makes the top lists when it comes to VoIP phone services. PCMag rated it the #1 business phone system for its reliability.

Ooma offers a lot of advanced features, even with its standard packages. However, certain ones, like video conferencing, are only available with the Office Pro package. That puts it a step below other communication systems, like Vonage, which offers video capabilities with their more basic packages.

Ooma stands out above the rest for its call quality with crystal clear sound that goes beyond other highly rated systems.

For what you get, Ooma is priced comparably to Vonage. However, others, like magicJack, are much less expensive for similar features. Ooma also doesn’t have an annual payment discount like many other companies do.

Ooma’s international calling can get pricey. If you do a lot of international calling, you might want to look into magicJack, which gives you free credits for international magicJack users. However, Ooma’s portability rivals other systems with its desktop and mobile apps that provide you with access on the go.

Ooma Call Quality

Call quality is one of the most important features when it comes to getting phone service. Without good call quality, you can’t offer optimal customer service. You want your customers to know you’re listening to their questions and concerns and are giving them the best information possible.

Ooma is praised for its superior high-definition call quality both domestically and internationally. It has better call quality ratings than most of the top-performing VoIP systems out there, including Vonage, magicJack, and RingCentral.

Ooma is also known for its excellent service with close to 100% uptime. With that, it rivals other top brands, like Nextiva.

Its system works well on both its desktop and mobile apps. You can even transfer your existing phone number or choose a new one in your area code. No matter how you use Ooma, it maintains consistently optimal call quality.

Ooma Pricing

Ooma has fair prices for their service and features, but they’re not the best you can find. Ooma comes in three packages:

  • Ooma Office: $19.95 per user per month
  • Ooma Office Pro: $24.95 per user per month
  • Ooma Enterprise: $27.99 per user per month

You get a pretty good deal with Ooma Office, with 35 call features for your business. However, it also leaves off some features that most companies offer with more basic packages. When you’re paying per user, you want to get as many features as possible in the cheapest package.

Without these features, you might find that your company has more restricted communication. You also only get the following features when you upgrade to Office Pro:

  • Desktop app
  • Enhanced call blocking
  • Voicemail transcription
  • Call recording

Once you get to Ooma Enterprise, you can use its analytics features, along with customizable call flows and APIs with only a small price jump.

However, if you’re looking to get more features at a better price, try Vonage Premium. They have many of the same offerings as Ooma but for a lower price, including video conferencing. You might also consider magicJack, which starts at $39 per year.

Ooma Mobile and Remote Features

With so many people working from home, remote features and mobile apps for VoIP systems have become vital in running a business. Without this feature, you leave out a significant portion of your demographic. It also makes it more difficult for your team members to do their jobs and connect with customers.

Ooma gives you mobile features using data or Wi-Fi to support remote workers alongside in-office teams. Remote employees can also use the desktop app to make calls from their computers.

You can forward calls and voicemails to mobile devices, making it easy to transfer calls on the go and doesn’t restrict any employee to one location. Plus, with virtual extension, multi-ring, and 2-phone-in-1, you can separate your business and personal numbers.

The mobile app isn’t unique to Ooma by itself, but it’s a feature that you’ll need to grow your business and keep it flexible. Ooma’s remote features give you everything you need to do that.

Ooma Basic Calling Features

While Ooma doesn’t offer as much in the way of advanced features to regular Office users, it has several basic features that make it a worthy choice for large and small businesses. It comes with features like:

  • Virtual receptionist
  • Mobile app
  • Call log
  • Call transfer
  • Multi-device ring
  • Call forwarding during outages
  • SMS messaging
  • Intercom

That’s a lot more features than Vonage’s lowest-priced package. While Vonage is the closest comparable VoIP service to Ooma, its Premium package gives you some of what you’re missing from Ooma’s Office package while leaving out others. For example, Vonage doesn’t come with a virtual receptionist. Whether you prefer Ooma, Vonage, or another service depends on which features you’ll use most for your business.

Ooma Customer Support

Ooma offers 24/7 customer support for all its packages, which doesn’t come with every VoIP service out there. Many services only offer priority support with more expensive packages.

You can look at that in two ways. First, you get better customer service with the basic Office package. But if you get Office Pro or Enterprise, the bump in price doesn’t include a bump to the front of the line if something goes wrong. In other words, no priority support. You wait just as long as someone who bought the cheapest package.

If priority customer support is important to you, try RingCentral. They earned the #1 spot for customer support, so you’ll know you’re getting the best care.

Ooma International Calls

International calling features should expand your company’s reach and let you collaborate with international businesses, suppliers, and customers. Not having this feature can limit your business’s growth.

Ooma does have international calling, but it’s more expensive than options like RingCentral. However, Ooma comes with international numbers and serves global companies as well as US-based ones, earning them a reputation for worldwide service.

Ooma offers almost the exact same international calling features as Vonage. Both allow outgoing calls to 60 countries on landlines and 10 countries for mobile numbers.

Though Ooma has a more expensive international calling service than magicJack, magicJack has its own limiting feature. The company only allows you to call other international magicJack users, limiting the number of businesses you can reach across the world. But if you need a cheaper option with unlimited international calling, you’ll find it with magicJack, as long as you don’t mind its restrictions.

Ooma Ring Groups

Ring groups ensure that someone is available for every caller. Ooma’s ring group feature means that when someone calls your business, the call goes to multiple lines until someone answers it. That can mean a group of customer service or sales representatives, as well as several people within a particular department.

The call might ring on multiple phones at once. Alternatively, it may ring on one phone, and if the first person doesn’t answer, the call will go through every phone in that specific ring group until someone picks up.

Ring groups lower the risk of customer calls going unanswered or going to voicemail. They also help you create a better overall customer experience when customers feel that someone is always available to help them.

Ooma Video Conferencing

We’ve already talked a little about Ooma’s video conferencing feature in that it only exists for Office Pro and Enterprise packages. But if you get Pro or higher, you get a high-quality system with an intuitive interface.

Video conferencing with Ooma gives you a click-to-join meeting system along with more advanced features, including:

  • Password protected meetings
  • Chat features
  • Mute options

It lets two or more people share their screens at the same time, making collaboration and visualization easier for team projects.

You can have up to 25 participants on video conference meetings. You can also create recurring meetings and view and search for your sessions on the platform all in one place. The features Ooma offers with its video conferencing make it worth springing for the Office Pro package.

Ooma Call Recording

Call recording features work well if you need to take notes to remind yourself of what was said during customer conversations. They also allow you to check the accuracy of the information and analyze conversations after they happen.

This feature comes with Ooma Office Pro. It puts your recordings in call logs to keep them all organized and saved in one convenient location, so you have access whenever you need them. The system is set up so you can listen and delete the recordings when you no longer need them.

Call recording is a must for communication systems. It means you don’t miss a beat, never have inaccurate information, and you can always reference customer and business calls at any time.

VoIP Phone Systems

VoIP phone systems come with more features and versatility than your typical handset. They’re also more cost-effective and function better for your business because they store everything in one place.

Ooma’s VoIP communication service can make calls from Android and iOS, so you’re not tied to your desk. It works well if you’re a small business that needs a Unified Communications as a Service (UCaaS) cloud-based system.

It’s also great for businesses that need to modernize and use better technology to meet customer needs. In other words, if you want to scale your business, you need a VoIP system.

Ooma’s VoIP service allows you to do work remotely. It offers features like:

  • Voice calling
  • Chat
  • Video
  • Fax

Ooma’s VoIP system has the features you need to meet customer expectations and keep your team running efficiently. Plus, with its mobile tools, you can connect your phone system to your mobile device and have access from your computer with the desktop app.

Hosted PBX Solutions

With this cloud-based system, you can add and remove lines as your business grows. That way, you don’t have to worry about clunky systems or spending extra time to make your system’s capability match your business’s size.

Ooma’s PBX solutions offer a quick and easy setup that Ooma helps you with, so you get everything up and running fast. Once it’s all in place, you’ve got a low maintenance system, and Ooma takes care of most of it for you.

PBX solutions work best for enterprise businesses. You get email, a mobile fax app, and a desktop app, along with all the usual features you would expect from your communication service. The UCaaS platform keeps your team cohesive.

With unlimited extensions, you don’t have to worry about your business getting too big for your phone system. You also enjoy free upgrades with no extra costs and no interruptions to your business operations. Ooma’s PBX solutions allow for efficient global communications, no matter who you need to reach.

Compare The Best VoIP Phone Services
We reviewed dozens of VoIP phone service providers and narrowed them down to the best options.
See Top Picks

Summary

Among the top-rated business phone systems available, Ooma gives you a versatile interface to make customer service operations simple for your team. With over 35 features in the Office package, it’s not hard to find everything you need with this system.

Office Pro and Enterprise only sweeten the deal with more advanced features. However, if you prioritize features like video conferencing, check other companies for cheaper packages that include it.

That said, Ooma has reasonable pricing, excellent call quality, and plenty of mobile and desktop features that allow you to call domestically and internationally. The company has solutions for both small businesses and large enterprises. No matter where you do business, Ooma can help you improve the way you communicate.

Hi everyone, I’m bruce.hagen

I work for a company based in NY. Air Techniques a manufacturer of dental equipment such as compressors, vacuums and digital scanners. I have been with the company for 23 years, I troubleshoot problems with technicians in the field. I also write troibleshooting scripts and schematics. My major accomplishments are i Excel. I have aproximately 50 macro enabled trobleshooting scripts. I am new to Python but am willing to learn.

Create Responsive Image Effects With CSS Gradients And `aspect-ratio`

To prepare for our future image effects, we’re going to set up a card component that has a large image at the top followed by a headline and description. The common problem with this setup is that we may not always have perfect control over what the image is, and more importantly to our layout, what its dimensions are. And while this can be resolved by cropping ahead of time, we can still encounter issues due to responsively sized containers. A consequence is uneven positions of the card content which really stands out when you present a row of cards.

Another previous solution besides cropping may have been to swap from an inline img to a blank div that only existed to present the image via background-image. I’ve implemented this solution many times myself in the past. One advantage this has is using an older trick for aspect ratio which uses a zero-height element and sets a padding-bottom value. Setting a padding value as a percent results in a final computed value that is relative to the element’s width. You may have also used this idea to maintain a 16:9 ratio for video embeds, in which case the padding value is found with the formula: 9/16 = 0.5625 * 100% = 56.26%. But we’re going to explore two modern CSS properties that don’t involve extra math, give us more flexibility, and also allow keeping the semantics provided by using a real img instead of an empty div.

First, let’s define the HTML semantics, including use of an unordered list as the cards’ container:

<ul class="card-wrapper">
  <li class="card">
    <img src="" alt="">
    <h3>A Super Wonderful Headline</h3>
    <p>Lorem ipsum sit dolor amit</p>
  </li>
  <!-- additional cards -->
</ul>

Next, we’ll create a minimal set of baseline styles for the .card component. We’ll set some basic visual styles for the card itself, a quick update to the expected h3 headline, then essential styles to begin to style the card image.

.card {
  background-color: #fff;
  border-radius: 0.5rem;
  box-shadow: 0.05rem 0.1rem 0.3rem -0.03rem rgba(0, 0, 0, 0.45);
  padding-bottom: 1rem;
}

.card > :last-child {
  margin-bottom: 0;
}

.card h3 {
  margin-top: 1rem;
  font-size: 1.25rem;
}

img {
  border-radius: 0.5rem 0.5rem 0 0;
  width: 100%;
}

img ~ * {
  margin-left: 1rem;
  margin-right: 1rem;
}

The last rule uses the general sibling combinator to add a horizontal margin to any element that follows the img since we want the image itself to be flush with the sides of the card.

And our progress so far leads us to the following card appearance:

Finally, we’ll create the .card-wrapper styles for a quick responsive layout using CSS grid. This will also remove the default list styles.

.card-wrapper {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(30ch, 1fr));
  grid-gap: 1.5rem;
}

Note: If this grid technique is unfamiliar to you, review the explanation in my tutorial about modern solutions for the 12-column grid.

With this applied and with all cards containing an image with a valid source path, our .card-wrapper styles give us the following layout:

As demonstrated in the preview image, these baseline styles aren’t enough to properly contain the images given their varying natural dimensions. We’re in need of a method to constrain these images uniformly and consistently.

Enable Uniform Image Sizes with object-fit

As noted earlier, you may previously have made an update in this scenario to change the images to be added via background-image instead and used background-size: cover to handle nicely resizing the image. Or you may have tried to enforce cropping ahead of time (still a worthy goal since any image size reduction will improve performance!).

Now, we have the property object-fit available which enables an img tag to act as the container for the image. And, it comes with a cover value as well that results in a similar effect as the background image solution, but with the bonus of retaining the semantics of an inline image. Let’s apply it and see how it works.

We do need to pair it with a height dimension for extra guidance on how we want the image container to behave (recall we had already added width: 100%). And we’re going to use the max() function to select either 10rem or 30vh depending on which is larger in a given context, which prevents the image height from shrinking too much on smaller viewports or when the user has set a large zoom.

img {
  /* ...existing styles */
  object-fit: cover;
  height: max(10rem, 30vh);
}

Bonus Accessibility Tip: You should always test your layouts with 200% and 400% zoom on desktop. While there isn’t currently a zoom media query, functions like max() can help resolve layout issues. Another context this technique is useful is spacing between elements.

With this update, we’ve definitely improved things, and the visual result is as if we’d use the older background image technique:

Responsively Consistent Image Sizing With aspect-ratio

When using object-fit by itself, one downside is that we still need to set some dimension hints.

An upcoming property (currently available in Chromium browsers) called aspect-ratio will enhance our ability to consistently size images.

Using this property, we can define a ratio to resize the image instead of setting explicit dimensions. We’ll continue to use it in combination with object-fit to ensure these dimensions only affect the image as a container, otherwise, the image could appear distorted.

Here is our full updated image rule:

img {
  border-radius: 0.5rem 0.5rem 0 0;
  width: 100%;
  object-fit: cover;
  aspect-ratio: 4/3;
}

We’re going to start with an image ratio of 4/3 for our card context, but you could choose any ratio. For example, 1/1 for a square, or 16/9 for standard video embeds.

Here are the updated cards, although it will probably be difficult to notice the visual difference in this particular instance since the aspect ratio happens to closely match the appearance we achieved by setting the height for object-fit alone.

Setting an aspect-ratio results in the ratio being maintained as the elements grow or shrink, whereas when only setting object-fit and height the image ratio will constantly be in flux as the container dimensions change.

Adding Responsive Effects With CSS Gradients And Functions

OK, now that we know how to setup consistently sized images, let’s have some fun with them by adding a gradient effect!

Our goal with this effect is to make it appear as though the image is fading into the card content. You may be tempted to wrap the image in its own container to add the gradient, but thanks to the work we’ve already done on the image sizing, we can work out how to safely do it on the main .card.

The first step is to define a gradient. We’re going to use a CSS custom property to add in the gradient colors to enable easily swapping the gradient effect, starting with a blue to pink. The last color in the gradient will always be white to maintain the transition into the card content background and create the “feathered” edge.

.card {
  --card-gradient: #5E9AD9, #E271AD;

  background-image: linear-gradient(
    var(--card-gradient),
    white max(9.5rem, 27vh)
  );
  /* ...existing styles */
}

But wait — is that a CSS max() function? In a gradient? Yes, it’s possible, and it’s the magic that makes this gradient effective responsively!

However, if I were to add a screenshot, we wouldn’t actually see the gradient having any effect on the image yet. For that, we need to bring in the mix-blend-mode property, and in this scenario we’ll use the overlay value:

img {
  /* ...existing styles */
  mix-blend-mode: overlay;
}

The mix-blend-mode property is similar to applying the layer blending styles available in photo manipulation software like Photoshop. And the overlay value will have the effect of allowing the medium tones in the image to blend with the gradient behind it, leading to the following result:

Now, at this point, we are relying on the aspect-ratio value alone to resize the image. And if we resize the container and cause the card layout to reflow, the changing image height causes inconsistencies in where the gradient fades to white.

So we’ll add a max-height property as well that also uses the max() function and contains values slightly greater than the ones in the gradient. The resulting behavior is that the gradient will (almost always) correctly line up with the bottom of the image.

img {
  /* ...existing styles */
  max-height: max(10rem, 30vh);
}

It’s important to note that adding a max-height alters the aspect-ratio behavior. Instead of always using the exact ratio, it will be used only when there’s enough allotted space given the new extra constraint of the max-height.

However, aspect-ratio will still continue to ensure the images resize consistently as was the benefit over only object-fit. Try commenting out aspect-ratio in the final CodePen demo to see the difference it’s making across container sizes.

Since our original goal was to enable consistently responsive image dimensions, we’ve still hit the mark. For your own use case, you may need to fiddle with the ratio and height values to achieve your desired effect.

Alternate: mix-blend-mode And Adding A Filter

Using overlay as the mix-blend-mode value was the best choice for the fade-to-white effect we were looking for, but let’s try an alternate option for a more dramatic effect.

We’re going to update our solution to add a CSS custom property for the mix-blend-mode value and also update the color values for the gradient:

.card {
  --card-gradient: tomato, orange;
  --card-blend-mode: multiply;
}

img {
  /* ...existing styles */
  mix-blend-mode: var(--card-blend-mode);
}

The multiply value has a darkening effect on mid-tones, but keeps white and black as is, resulting in the following appearance:

While we’ve lost the fade and now have a hard edge on the bottom of the image, the white part of our gradient is still important to ensure that the gradient ends prior to the card content.

One additional modification we can add is the use of filter and, in particular, use the grayscale() function to remove the image colors and therefore have the gradient be the only source of image coloring.

img {
  /* ...existing styles */
  filter: grayscale(100);
}

Using the value of grayscale(100) results in complete removal of the image’s natural colors and transforming it into black and white. Here’s the update for comparison with the previous screenshot of its effect when using our orange gradient with multiply:

Use aspect-ratio As A Progressive Enhancement

As previously mentioned, currently aspect-ratio is only supported in the latest version of Chromium browsers (Chrome and Edge). However, all browsers support object-fit and that along with our height constraints results in a less-ideal but still acceptable result, seen here for Safari:

Without aspect-ratio functioning, the result here is that ultimately the image height is capped but the natural dimensions of each image still lead to some variance between card image heights. You may want to instead change to adding a max-height or make use of the max() function again to help make a max-height more responsive across varying card sizes.

Extending The Gradient Effects

Since we defined the gradient color stops as a CSS custom property, we have ready access to change them under different contexts. For example, we might change the gradient to more strongly feature one of the colors if the card is hovered or has one of its children in focus.

First, we’ll update each card h3 to contain a link, such as:

<h3><a href="">A Super Wonderful Headline</a></h3>

Then, we can use one of our newest available selectors — :focus-within — to alter the card gradient when the link is in focus. For extra coverage of possible interactions, we’ll couple this with :hover. And, we’ll reuse our max() idea to assign a single color to take over coverage of the image portion of the card. The downside to this particular effect is that gradient stops and color changes aren’t reliably animateable — but they will be soon thanks to CSS Houdini.

To update the color and add the new color stop, we just need to re-assign the value of --card-gradient within this new rule:

.card:focus-within,
.card:hover {
  --card-gradient: #24a9d5 max(8.5rem, 20vh);
}

Our max() values are less than the original in use for white to maintain the feathered edge. If we used the same values, it would meet the white and create a clearly straightedge separation.

In creating this demo, I originally tried an effect that used transform with scale for a zoom-in effect. But I discovered that due to mix-blend-mode being applied, the browser would not consistently repaint the image which caused an unpleasant flickering. There will always be trade-offs in requesting the browser perform CSS-only effects and animations, and while it’s very cool what we can do, it’s always best to check the performance impact of your effects.

Have Fun Experimenting!

Modern CSS has given us some awesome tools for updating our web design toolkits, with aspect-ratio being the latest addition. So go forth, and experiment with object-fit, aspect-ratio, and adding functions like max() into your gradients for some fun responsive effects! Just be sure to double-check things cross-browser (for now!) and across varying viewports and container sizes.

Here is the CodePen including the features and effects we reviewed today:

See the Pen Responsive Image Effects with CSS Gradients and aspect-ratio by Stephanie Eckles.

Looking for more? Make sure you check out our CSS Guide here on Smashing →

What’s Coming in WordPress 5.7 (Features and Screenshots)

WordPress 5.7 beta is out for a while now, and it is scheduled to be released on March 9th 2021. It will be the first major release of the year and comes with some new features and improvements.

We’ve been following the development closely and trying out the new features on our test sites.

In this article, we will show you what’s coming in WordPress 5.7 with features and screenshots.

What's coming in WordPress 5.7

Note: You can try out the beta version on your computer or on a staging environment by using the WordPress Beta Tester plugin.

WordPress 5.7 is under development and some features can still change and may not make it into the final release.

That being said, let’s take a look at what’s coming in WordPress 5.7.

Editor Improvements in WordPress 5.7

The WordPress editor is the area where website owners spend most of their time creating pages and writing content.

Each WordPress release brings new features and improvements to the editor. WordPress 5.7 will also ship with some exciting new features and enhancements to improve your editing experience.

Drag and Drop Blocks from Inserter

WordPress 5.7 will allow you to simply drag and drop a block from the ‘Add new block’ (+) inserter into your content area. This way you can choose where to place a block before you drop it on the post canvas.

Drag and drop a block from inserter

Full-height Blocks Coming in WordPress 5.7

Do you like using the full-width blocks for cover, columns, and group blocks? This allows you to create visually stunning layouts. WordPress 5.7 will now allow you to create full-height blocks too.

Full height option in a block toolbar

These blocks will fill in the height of the user’s screen allowing you to create even more engaging layouts.

Block Variations Can Now Have Their Own Descriptions

WordPress 5.7 will now show descriptions for block variations. For instance, the social icons block has Facebook, Twitter, and other social icons. You can view them in the block inspector with a preview.

Block variation descriptions

Social Icon Sizes

WordPress 5.7 will allow you to adjust the size of icons in the social media icons block.

Social icon size

Improved Buttons

WordPress 5.7 will introduce vertical alignment for buttons in the block editor. Users will also be able to choose from a preset percentage width for their buttons.

Vertical alignment for buttons

Adjust Font Size in More Blocks

Another improvement in the block editor is the ability to adjust the font size in more blocks including the list and code blocks.

Font sizes in more places in WordPress 5.7

Easier Migration from HTTP to HTTPS

Previously when you moved WordPress from HTTP to HTTPS you had to manually update URLs embedded in your content. Not doing so resulted in the mixed content issue.

WordPress 5.7 will make it easier to migrate your website to HTTPs. It will show the availability of HTTPS in the site health menu as a critical issue.

Migrate to HTTPs with one-click

From here, users will be able to click on a button to update the WordPress URLs. This will switch your WordPress and Site URL settings, and update URLs in your content to use HTTPs.

Standardize WP-Admin Color Standardize Palette

WordPress 5.7 will also standardize the wp-admin color palette. Basically, WordPress uses Sass to generate CSS files on the fly.

WordPress came with a limited set of color schemes and introducing new color schemes was hard as developers had to deal Sass variable-based system.

Admin color scheme palettes in WordPress

In the upcoming 5.7 release, WordPress will collapse all colors used in the CSS to one of the available shades of blue, green, red, yellow, grey, black, and white. This will provide developers a palette with a wider range of light and dark color schemes.

New Robots API

WordPress 5.7 will introduce a new Robots API. This API will allow developers to programmatically control and update the robots meta tag on a website.

Robots meta tag allows you to tell search engine bots how to crawl and index a website. This can be achieved by adding a robots.txt file to your site’s root folder or by using the robots meta tag.

A new function called wp_robots will be introduced in WordPress 5.7. Developers will be able to modify the robots meta tab by adding their own filters to the function. Here is an example of how it would work:

function wporg_wp_robots_add_follow( $robots ) {
    $robots['follow'] = true;
    return $robots;
}
add_filter( 'wp_robots', 'wporg_wp_robots_add_follow' );

WordPress 5.7 will also add a max-image-preview:large directive to the robots meta tag by default. This will allow search engines to use the large image for preview in search results.

Here is how it would look in your site’s source code:

<!DOCTYPE html>
<html>
    <head>
        <meta name="robots" content="max-image-preview:large" />
    </head>

WordPress will automatically hide this tag for websites that have search engine visibility turned off in the settings.

If you want to disable this and let search engines decide which image to use for the previews, then you can add the following code to your theme’s functions.php file or a site-specific plugin.

remove_filter( 'wp_robots', 'wp_robots_max_image_preview_large' );

Lazy Loading iFrames

WordPress 5.7 will automatically add lazy load to iframe embeds. Lazy Loading is a technique used to improve website speed during a page load. It basically tells the user’s browser to load an item when it is viewed.

WordPress already uses lazy loading for images by default since WordPress 5.5.

This means all embeds that use iframes, such as YouTube videos, will use lazy loading to improve your page load speeds.

If you are using Smash Balloon’s YouTube Feed, then it already optimizes your video feed by better caching and delayed loading.

Under The Hood Changes

WordPress 5.7 will also bring many changes for developers to explore and use in their own themes, plugins, and projects.

Following are some of these under the hood changes.

WordPress 5.7 will introduce new functions is_post_status_viewable() and is_post_publicly_viewable() to check if a post is publicly viewable. (See details)

Render block function will now allow developers to filter the content of a single block. (#46187)

WordPress 5.7 will add a filter hook to manipulate $user_data variable during retrieve_password() function. This will enable developers to perform custom validation checks during password resets. (#51924)

Developers will be able to change the ‘Go to site’ link displayed in the login page footer by using a new login_site_html_link hook. (#35449)

Two new functions get_post_parent() and has_post_parent() will be available in WordPress 5.7 to determine if a post has a parent and to get the related parent post as an object. (#33045)

We hope this article gave you a good idea of what is coming in the upcoming WordPress 5.7. Let us know what features you find interesting and what you’d look to see in a future WordPress release.

If you liked this article, then please subscribe to our YouTube Channel for WordPress video tutorials. You can also find us on Twitter and Facebook.

The post What’s Coming in WordPress 5.7 (Features and Screenshots) appeared first on WPBeginner.

Creating an Infinite Circular Gallery using WebGL with OGL and GLSL Shaders

In this tutorial we’ll implement an infinite circular gallery using WebGL with OGL based on the website Lions Good News 2020 made by SHIFTBRAIN inc.

Most of the steps of this tutorial can be also reproduced in other WebGL libraries such as Three.js or Babylon.js with the correct adaptations.

With that being said, let’s start coding!

Creating our OGL 3D environment

The first step of any WebGL tutorial is making sure that you’re setting up all the rendering logic required to create a 3D environment.

Usually what’s required is: a camera, a scene and a renderer that is going to output everything into a canvas element. Then inside a requestAnimationFrame loop, you’ll use your camera to render a scene inside the renderer. So here’s our initial snippet:

import { Renderer, Camera, Transform } from 'ogl'
 
export default class App {
  constructor () {
    this.createRenderer()
    this.createCamera()
    this.createScene()
 
    this.onResize()
 
    this.update()
 
    this.addEventListeners()
  }
 
  createRenderer () {
    this.renderer = new Renderer()
 
    this.gl = this.renderer.gl
    this.gl.clearColor(0.79607843137, 0.79215686274, 0.74117647058, 1)
 
    document.body.appendChild(this.gl.canvas)
  }
 
  createCamera () {
    this.camera = new Camera(this.gl)
    this.camera.fov = 45
    this.camera.position.z = 20
  }
 
  createScene () {
    this.scene = new Transform()
  }
 
  /**
   * Events.
   */
  onTouchDown (event) {
      
  }
 
  onTouchMove (event) {
      
  }
 
  onTouchUp (event) {
      
  }
 
  onWheel (event) {
      
  }
 
  /**
   * Resize.
   */
  onResize () {
    this.screen = {
      height: window.innerHeight,
      width: window.innerWidth
    }
 
    this.renderer.setSize(this.screen.width, this.screen.height)
 
    this.camera.perspective({
      aspect: this.gl.canvas.width / this.gl.canvas.height
    })
 
    const fov = this.camera.fov * (Math.PI / 180)
    const height = 2 * Math.tan(fov / 2) * this.camera.position.z
    const width = height * this.camera.aspect
 
    this.viewport = {
      height,
      width
    }
  }
 
  /**
   * Update.
   */
  update () {
    this.renderer.render({
      scene: this.scene,
      camera: this.camera
    })
    
    window.requestAnimationFrame(this.update.bind(this))
  }
 
  /**
   * Listeners.
   */
  addEventListeners () {
    window.addEventListener('resize', this.onResize.bind(this))
 
    window.addEventListener('mousewheel', this.onWheel.bind(this))
    window.addEventListener('wheel', this.onWheel.bind(this))
 
    window.addEventListener('mousedown', this.onTouchDown.bind(this))
    window.addEventListener('mousemove', this.onTouchMove.bind(this))
    window.addEventListener('mouseup', this.onTouchUp.bind(this))
 
    window.addEventListener('touchstart', this.onTouchDown.bind(this))
    window.addEventListener('touchmove', this.onTouchMove.bind(this))
    window.addEventListener('touchend', this.onTouchUp.bind(this))
  }
}
 
new App()

Explaining the App class setup

In our createRenderer method, we’re initializing a renderer with a fixed color background by calling this.gl.clearColor. Then we’re storing our GL context (this.renderer.gl) reference in the this.gl variable and appending our <canvas> (this.gl.canvas) element to our document.body.

In our createCamera method, we’re creating a new Camera() instance and setting some of its attributes: fov and its z position. The FOV is the field of view of your camera, what you’re able to see from it. And the z is the position of your camera in the z axis.

In our createScene method, we’re using the Transform class, that is the representation of a new scene that is going to contain all our planes that represent our images in the WebGL environment.

The onResize method is the most important part of our initial setup. It’s responsible for three different things:

  1. Making sure we’re always resizing the <canvas> element with the correct viewport sizes.
  2. Updating our this.camera perspective dividing the width and height of the viewport.
  3. Storing in the variable this.viewport, the value representations that will help to transform pixels into 3D environment sizes by using the fov from the camera.

The approach of using the camera.fov to transform pixels in 3D environment sizes is an approach used very often in multiple WebGL implementations. Basically what it does is making sure that if we do something like: this.mesh.scale.x = this.viewport.width; it’s going to make our mesh fit the entire screen width, behaving like width: 100%, but in 3D space.

And finally in our update, we’re setting our requestAnimationFrame loop and making sure we keep rendering our scene.

You’ll also notice that we already included the wheel, touchstart, touchmove, touchend, mousedown, mousemove and mouseup events, they will be used to include user interactions with our application.

Creating a reusable geometry instance

It’s a good practice to keep memory usage low by always reusing the same geometry reference no matter what WebGL library you’re using. To represent all our images, we’re going to use a Plane geometry, so let’s create a new method and store this new geometry inside the this.planeGeometry variable.

import { Renderer, Camera, Transform, Plane } from 'ogl'
 
createGeometry () {
  this.planeGeometry = new Plane(this.gl, {
    heightSegments: 50,
    widthSegments: 100
  })
}

The reason for including heightSegments and widthSegments with these values is being able to manipulate vertices in a way to make the Plane behave like a paper in the air.

Importing our images using Webpack

Now it’s time to import our images into our application. Since we’re using Webpack in this tutorial, all we need to do to request our images is using import:

import Image1 from 'images/1.jpg'
import Image2 from 'images/2.jpg'
import Image3 from 'images/3.jpg'
import Image4 from 'images/4.jpg'
import Image5 from 'images/5.jpg'
import Image6 from 'images/6.jpg'
import Image7 from 'images/7.jpg'
import Image8 from 'images/8.jpg'
import Image9 from 'images/9.jpg'
import Image10 from 'images/10.jpg'
import Image11 from 'images/11.jpg'
import Image12 from 'images/12.jpg'

Now let’s create our array of images that we want to use in our infinite slider, so we’re basically going to call the variables above inside a createMedia method, and use .map to create new instances of the Media class (new Media()), which is going to be our representation of each image of the gallery.

createMedias () {
  this.mediasImages = [
    { image: Image1, text: 'New Synagogue' },
    { image: Image2, text: 'Paro Taktsang' },
    { image: Image3, text: 'Petra' },
    { image: Image4, text: 'Gooderham Building' },
    { image: Image5, text: 'Catherine Palace' },
    { image: Image6, text: 'Sheikh Zayed Mosque' },
    { image: Image7, text: 'Madonna Corona' },
    { image: Image8, text: 'Plaza de Espana' },
    { image: Image9, text: 'Saint Martin' },
    { image: Image10, text: 'Tugela Falls' },
    { image: Image11, text: 'Sintra-Cascais' },
    { image: Image12, text: 'The Prophet\'s Mosque' },
    { image: Image1, text: 'New Synagogue' },
    { image: Image2, text: 'Paro Taktsang' },
    { image: Image3, text: 'Petra' },
    { image: Image4, text: 'Gooderham Building' },
    { image: Image5, text: 'Catherine Palace' },
    { image: Image6, text: 'Sheikh Zayed Mosque' },
    { image: Image7, text: 'Madonna Corona' },
    { image: Image8, text: 'Plaza de Espana' },
    { image: Image9, text: 'Saint Martin' },
    { image: Image10, text: 'Tugela Falls' },
    { image: Image11, text: 'Sintra-Cascais' },
    { image: Image12, text: 'The Prophet\'s Mosque' },
  ]
 
 
  this.medias = this.mediasImages.map(({ image, text }, index) => {
    const media = new Media({
      geometry: this.planeGeometry,
      gl: this.gl,
      image,
      index,
      length: this.mediasImages.length,
      scene: this.scene,
      screen: this.screen,
      text,
      viewport: this.viewport
    })
 
    return media
  })
}

As you’ve probably noticed, we’re passing a bunch of arguments to our Media class, I’ll explain why they’re needed when we start setting up the class in the next section. We’re also duplicating the amount of images to avoid any issues of not having enough images when making our gallery infinite on very wide screens.

It’s important to also include some specific calls in the onResize and update methods for our this.medias array, because we want the images to be responsive:

onResize () {
  if (this.medias) {
    this.medias.forEach(media => media.onResize({
      screen: this.screen,
      viewport: this.viewport
    }))
  }
}

And also do some real-time manipulations inside the requestAnimationFrame:

update () {
  this.medias.forEach(media => media.update(this.scroll, this.direction))
}

Setting up the Media class

Our Media class is going to use Mesh, Program and Texture classes from OGL to create a 3D plane and attribute a texture to it, which in our case is going to be our images.

In our constructor, we’re going to store all variables that we need and that were passed in the new Media() initialization from index.js:

export default class {
  constructor ({ geometry, gl, image, index, length, renderer, scene, screen, text, viewport }) {
    this.geometry = geometry
    this.gl = gl
    this.image = image
    this.index = index
    this.length = length
    this.scene = scene
    this.screen = screen
    this.text = text
    this.viewport = viewport
 
    this.createShader()
    this.createMesh()
 
    this.onResize()
  }
}

Explaining a few of these arguments, basically the geometry is the geometry we’re going to apply to our Mesh class. The this.gl is our GL context, useful to keep doing WebGL manipulations inside the class. The this.image is the URL of the image. Both of the this.index and this.length will be used to do positions calculations of the mesh. The this.scene is the group which we’re going to append our mesh to. And finally this.screen and this.viewport are the sizes of the viewport and environment.

Now it’s time to create the shader that is going to be applied to our Mesh in the createShader method, in OGL shaders are created with Program:

createShader () {
  const texture = new Texture(this.gl, {
    generateMipmaps: false
  })
 
  this.program = new Program(this.gl, {
    fragment,
    vertex,
    uniforms: {
      tMap: { value: texture },
      uPlaneSizes: { value: [0, 0] },
      uImageSizes: { value: [0, 0] },
      uViewportSizes: { value: [this.viewport.width, this.viewport.height] }
      },
    transparent: true
  })
 
  const image = new Image()
 
  image.src = this.image
  image.onload = _ => {
    texture.image = image
 
    this.program.uniforms.uImageSizes.value = [image.naturalWidth, image.naturalHeight]
  }
}

In the snippet above, we’re basically creating a new Texture() instance, making sure to use generateMipmaps as false so it preserves the quality of the image. Then creating a new Program() instance, which represents a shader composed of fragment and vertex with some uniforms used to manipulate it.

We’re also creating a new Image() instance to preload the image before applying it to the texture.image. And also updating the this.program.uniforms.uImageSizes.value because it’s going to be used to preserve the aspect ratio of our images.

It’s important to create our fragment and vertex shaders now, so we’re going to create two new files: fragment.glsl and vertex.glsl:

precision highp float;
 
uniform vec2 uImageSizes;
uniform vec2 uPlaneSizes;
uniform sampler2D tMap;
 
varying vec2 vUv;
 
void main() {
  vec2 ratio = vec2(
    min((uPlaneSizes.x / uPlaneSizes.y) / (uImageSizes.x / uImageSizes.y), 1.0),
    min((uPlaneSizes.y / uPlaneSizes.x) / (uImageSizes.y / uImageSizes.x), 1.0)
  );
 
  vec2 uv = vec2(
    vUv.x * ratio.x + (1.0 - ratio.x) * 0.5,
    vUv.y * ratio.y + (1.0 - ratio.y) * 0.5
  );
 
  gl_FragColor.rgb = texture2D(tMap, uv).rgb;
  gl_FragColor.a = 1.0;
}
precision highp float;
 
attribute vec3 position;
attribute vec2 uv;
 
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
 
varying vec2 vUv;
 
void main() {
  vUv = uv;
 
  vec3 p = position;
 
  gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
}

And require them in the start of Media.js using Webpack:

import fragment from './fragment.glsl'
import vertex from './vertex.glsl'

Now let’s create our new Mesh() instance in the createMesh method merging together the geometry and shader.

createMesh () {
  this.plane = new Mesh(this.gl, {
    geometry: this.geometry,
    program: this.program
  })
 
  this.plane.setParent(this.scene)
}

The Mesh instance is stored in the this.plane variable to be reused in the onResize and update methods, then appended as a child of the this.scene group.

The only thing we have now on the screen is a simple square with our image:

Let’s now implement the onResize method and make sure we’re rendering rectangles:

onResize ({ screen, viewport } = {}) {
  if (screen) {
    this.screen = screen
  }
 
  if (viewport) {
    this.viewport = viewport
 
    this.plane.program.uniforms.uViewportSizes.value = [this.viewport.width, this.viewport.height]
  }
 
  this.scale = this.screen.height / 1500
 
  this.plane.scale.y = this.viewport.height * (900 * this.scale) / this.screen.height
  this.plane.scale.x = this.viewport.width * (700 * this.scale) / this.screen.width
 
  this.plane.program.uniforms.uPlaneSizes.value = [this.plane.scale.x, this.plane.scale.y]
}

The scale.y and scale.x calls are responsible for scaling our element properly, transforming our previous square into a rectangle of 700×900 sizes based on the scale.

And the uViewportSizes and uPlaneSizes uniform value updates makes the image display correctly. That’s basically what makes the image have the background-size: cover; behavior, but in WebGL environment.

Now we need to position all the rectangles in the x axis, making sure we have a small gap between them. To achieve that, we’re going to use this.plane.scale.x, this.padding and this.index variables to do the calculation required to move them:

this.padding = 2
 
this.width = this.plane.scale.x + this.padding
this.widthTotal = this.width * this.length
 
this.x = this.width * this.index

And in the update method, we’re going to set the this.plane.position to these variables:

update () {
  this.plane.position.x = this.x
}

Now you’ve setup all the initial code of Media, which results in the following image:

Including infinite scrolling logic

Now it’s time to make it interesting and include scrolling logic on it, so we have at least an infinite gallery in place when the user scrolls through your page. In our index.js, we’ll do the following updates.

First, let’s include a new object called this.scroll in our constructor with all variables that we will manipulate to do the smooth scrolling:

this.scroll = {
  ease: 0.05,
  current: 0,
  target: 0,
  last: 0
}

Now let’s add the touch and wheel events, so when the user interacts with the canvas, he will be able to move stuff:

onTouchDown (event) {
  this.isDown = true
 
  this.scroll.position = this.scroll.current
  this.start = event.touches ? event.touches[0].clientX : event.clientX
}
 
onTouchMove (event) {
  if (!this.isDown) return
 
  const x = event.touches ? event.touches[0].clientX : event.clientX
  const distance = (this.start - x) * 0.01
 
  this.scroll.target = this.scroll.position + distance
}
 
onTouchUp (event) {
  this.isDown = false
}

Then, we’ll include the NormalizeWheel library in onWheel event, so this way we have the same value on all browsers when the user scrolls:

onWheel (event) {
  const normalized = NormalizeWheel(event)
  const speed = normalized.pixelY
 
  this.scroll.target += speed * 0.005
}

In our update method with requestAnimationFrame, we’ll lerp the this.scroll.current with this.scroll.target to make it smooth, then we’ll pass it to all medias:

update () {
  this.scroll.current = lerp(this.scroll.current, this.scroll.target, this.scroll.ease)
 
  if (this.medias) {
    this.medias.forEach(media => media.update(this.scroll))
  }
 
  this.scroll.last = this.scroll.current
 
  window.requestAnimationFrame(this.update.bind(this))
}

And now we just update our Media file to use the current scroll value to move the Mesh to the new scroll position:

update (scroll) {
  this.plane.position.x = this.x - scroll.current * 0.1
}

This is the current result we have:

As you’ve noticed, it’s not infinite yet, to achieve that, we need to include some extra code. The first step is including the direction of the scroll in the update method from index.js:

update () {
  this.scroll.current = lerp(this.scroll.current, this.scroll.target, this.scroll.ease)
 
  if (this.scroll.current > this.scroll.last) {
    this.direction = 'right'
  } else {
    this.direction = 'left'
  }
 
  if (this.medias) {
    this.medias.forEach(media => media.update(this.scroll, this.direction))
  }
 
  this.scroll.last = this.scroll.current
}

Now in the Media class, you need to include a variable called this.extra in the constructor, and do some manipulations on it to sum the total width of the gallery, when the element is outside of the screen.

constructor ({ geometry, gl, image, index, length, renderer, scene, screen, text, viewport }) {
  this.extra = 0
}

update (scroll) {
  this.plane.position.x = this.x - scroll.current * 0.1 - this.extra
    
  const planeOffset = this.plane.scale.x / 2
  const viewportOffset = this.viewport.width
 
  this.isBefore = this.plane.position.x + planeOffset < -viewportOffset
  this.isAfter = this.plane.position.x - planeOffset > viewportOffset
 
  if (direction === 'right' && this.isBefore) {
    this.extra -= this.widthTotal
 
    this.isBefore = false
    this.isAfter = false
  }
 
  if (direction === 'left' && this.isAfter) {
    this.extra += this.widthTotal
 
    this.isBefore = false
    this.isAfter = false
  }
}

That’s it, now we have the infinite scrolling gallery, pretty cool right?

Including circular rotation

Now it’s time to include the special flavor of the tutorial, which is making the infinite scrolling also have the circular rotation. To achieve it, we’ll use Math.cos to change the this.mesh.position.y accordingly to the rotation of the element. And map technique to change the this.mesh.rotation.z based on the element position in the z axis.

First, let’s make it rotate in a smooth way based on the position. The map method is basically a way to serve values based on another specific range, let’s say for example you use map(0.5, 0, 1, -500, 500);, it’s going to return 0 because it’s the middle between -500 and 500. Basically the first argument controls the output of min2 and max2:

export function map (num, min1, max1, min2, max2, round = false) {
  const num1 = (num - min1) / (max1 - min1)
  const num2 = (num1 * (max2 - min2)) + min2
 
  if (round) return Math.round(num2)
 
  return num2
}

Let’s see it in action by including the following like of code in the Media class:

this.plane.rotation.z = map(this.plane.position.x, -this.widthTotal, this.widthTotal, Math.PI, -Math.PI)

And that’s the result we get so far. It’s already pretty cool because you’re able to see the rotation changing based on the plane position:

Now it’s time to make it look circular. Let’s use Math.cos, we just need to do a simple calculation with this.plane.position.x / this.widthTotal, this way we’ll have a cos that will return a normalized value that we can just tweak multiplying by how much we want to change the y position of the element:

this.plane.position.y = Math.cos((this.plane.position.x / this.widthTotal) * Math.PI) * 75 - 75

Simple as that, we’re just moving it by 75 in environment space based in the position, this gives us the following result, which is exactly what we wanted to achieve:

Snapping to the closest item

Now let’s include a simple snapping to the closest item when the user stops scrolling. To achieve that, we need to create a new method called onCheck, it’s going to do some calculations when the user releases the scrolling:

onCheck () {
  const { width } = this.medias[0]
  const itemIndex = Math.round(Math.abs(this.scroll.target) / width)
  const item = width * itemIndex
 
  if (this.scroll.target < 0) {
    this.scroll.target = -item
  } else {
    this.scroll.target = item
  }
}

The result of the item variable is always the center of one of the elements in the gallery, which snaps the user to the corresponding position.

For wheel events, we need a debounced version of it called onCheckDebounce that we can include in the constructor by including lodash/debounce:

import debounce from 'lodash/debounce'
 
constructor ({ camera, color, gl, renderer, scene, screen, url, viewport }) {
  this.onCheckDebounce = debounce(this.onCheck, 200)
}
 
onWheel (event) {
  this.onCheckDebounce()
}

Now the gallery is always being snapped to the correct entry:

Writing paper shaders

Finally let’s include the most interesting part of our project, which is enhancing the shaders a little bit by taking into account the scroll velocity and distorting the vertices of our meshes.

The first step is to include two new uniforms in our this.program declaration from Media class: uSpeed and uTime.

this.program = new Program(this.gl, {
  fragment,
  vertex,
  uniforms: {
    tMap: { value: texture },
    uPlaneSizes: { value: [0, 0] },
    uImageSizes: { value: [0, 0] },
    uViewportSizes: { value: [this.viewport.width, this.viewport.height] },
    uSpeed: { value: 0 },
    uTime: { value: 0 }
  },
  transparent: true
})

Now let’s write some shader code to make our images bend and distort in a very cool way. In your vertex.glsl file, you should include the new uniforms: uniform float uTime and uniform float uSpeed:

uniform float uTime;
uniform float uSpeed;

Then inside the void main() of your shader, you can now manipulate the vertices in the z axis using these two values plus the position stored variable in p. We’re going to use a sin and cos to bend our vertices like it’s a plane, so all you need to do is including the following line:

p.z = (sin(p.x * 4.0 + uTime) * 1.5 + cos(p.y * 2.0 + uTime) * 1.5);

Also don’t forget to include uTime increment in the update() method from Media:

this.program.uniforms.uTime.value += 0.04

Just this line of code outputs a pretty cool paper effect animation:

Including text in WebGL using MSDF fonts

Now let’s include our text inside the WebGL, to achieve that, we’re going to use msdf-bmfont to generate our files, you can see how to do that in this GitHub repository, but basically it’s installing the npm dependency and running the command below:

msdf-bmfont -f json -m 1024,1024 -d 4 --pot --smart-size freight.otf

After running it, you should now have a .png and .json file in the same directory, these are the files that we’re going to use on our MSDF implementation in OGL.

Now let’s create a new file called Title and start setting up the code of it. First let’s create our class and use import in the shaders and the files:

import AutoBind from 'auto-bind'
import { Color, Geometry, Mesh, Program, Text, Texture } from 'ogl'
 
import fragment from 'shaders/text-fragment.glsl'
import vertex from 'shaders/text-vertex.glsl'
 
import font from 'fonts/freight.json'
import src from 'fonts/freight.png'
 
export default class {
  constructor ({ gl, plane, renderer, text }) {
    AutoBind(this)
 
    this.gl = gl
    this.plane = plane
    this.renderer = renderer
    this.text = text
 
    this.createShader()
    this.createMesh()
  }
}

Now it’s time to start setting up MSDF implementation code inside the createShader() method. The first thing we’re going to do is create a new Texture() instance and load the fonts/freight.png one stored in src:

createShader () {
  const texture = new Texture(this.gl, { generateMipmaps: false })
  const textureImage = new Image()
 
  textureImage.src = src
  textureImage.onload = _ => texture.image = textureImage
}

Then we need to start setting up the fragment shader we’re going to use to render the MSDF text, because MSDF can be optimized in WebGL 2.0, we’re going to use this.renderer.isWebgl2 from OGL to check if it’s supported or not and declare different shaders based on it, so we’ll have vertex300, fragment300, vertex100 and fragment100:

createShader () {
  const vertex100 = `${vertex}`
 
  const fragment100 = `
    #extension GL_OES_standard_derivatives : enable
 
    precision highp float;
 
    ${fragment}
  `
 
  const vertex300 = `#version 300 es
 
    #define attribute in
    #define varying out
 
    ${vertex}
  `
 
  const fragment300 = `#version 300 es
 
    precision highp float;
 
    #define varying in
    #define texture2D texture
    #define gl_FragColor FragColor
 
    out vec4 FragColor;
 
    ${fragment}
  `
 
  let fragmentShader = fragment100
  let vertexShader = vertex100
 
  if (this.renderer.isWebgl2) {
    fragmentShader = fragment300
    vertexShader = vertex300
  }
 
  this.program = new Program(this.gl, {
    cullFace: null,
    depthTest: false,
    depthWrite: false,
    transparent: true,
    fragment: fragmentShader,
    vertex: vertexShader,
    uniforms: {
      uColor: { value: new Color('#545050') },
      tMap: { value: texture }
    }
  })
}

As you’ve probably noticed, we’re prepending fragment and vertex with different setup based on the renderer WebGL version, let’s create also our text-fragment.glsl and text-vertex.glsl files:

uniform vec3 uColor;
uniform sampler2D tMap;
 
varying vec2 vUv;
 
void main() {
  vec3 color = texture2D(tMap, vUv).rgb;
 
  float signed = max(min(color.r, color.g), min(max(color.r, color.g), color.b)) - 0.5;
  float d = fwidth(signed);
  float alpha = smoothstep(-d, d, signed);
 
  if (alpha < 0.02) discard;
 
  gl_FragColor = vec4(uColor, alpha);
}
attribute vec2 uv;
attribute vec3 position;
 
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
 
varying vec2 vUv;
 
void main() {
  vUv = uv;
 
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

Finally let’s create the geometry of our MSDF font implementation in the createMesh() method, for that we’ll use the new Text() instance from OGL, and then apply the buffers generated from it to the new Geometry() instance:

createMesh () {
  const text = new Text({
    align: 'center',
    font,
    letterSpacing: -0.05,
    size: 0.08,
    text: this.text,
    wordSpacing: 0,
  })
 
  const geometry = new Geometry(this.gl, {
    position: { size: 3, data: text.buffers.position },
    uv: { size: 2, data: text.buffers.uv },
    id: { size: 1, data: text.buffers.id },
    index: { data: text.buffers.index }
  })
 
  geometry.computeBoundingBox()
 
  this.mesh = new Mesh(this.gl, { geometry, program: this.program })
  this.mesh.position.y = -this.plane.scale.y * 0.5 - 0.085
  this.mesh.setParent(this.plane)
}

Now let’s apply our brand new titles in the Media class, we’re going to create a new method called createTilte() and apply it to the constructor:

constructor ({ geometry, gl, image, index, length, renderer, scene, screen, text, viewport }) {
  this.createTitle()
}

createTitle () {
  this.title = new Title({
    gl: this.gl,
    plane: this.plane,
    renderer: this.renderer,
    text: this.text,
  })
}

Simple as that, we’re just including a new Title() instance inside our Media class, this will output the following result for you:

One of the best things about rendering text inside WebGL is reducing the overload of calculations required by the browser when animating the text to the right position. If you go with the DOM approach, you’ll usually have a little bit of performance impact because browsers will need to recalculate DOM sections when translating the text properly and checking composite layers.

For the purpose of this demo, we also included a new Number() class implementation that will be responsible for showing the current index that the user is seeing. You can check how it’s implemented in source code, but it’s basically the same implementation of the Title class with the only difference of it loading a different font style:

Including background blocks

To finalize the demo, let’s implement some blocks in the background that will be moving in x and y axis to enhance the depth effect of it:

To achieve this effect we’re going to create a new Background class and inside of it we’ll initialize some new Plane() geometries in a new Mesh() with random sizes and positions by changing the scale and position of the meshes of the for loop:

import { Color, Mesh, Plane, Program } from 'ogl'
 
import fragment from 'shaders/background-fragment.glsl'
import vertex from 'shaders/background-vertex.glsl'
 
import { random } from 'utils/math'
 
export default class {
  constructor ({ gl, scene, viewport }) {
    this.gl = gl
    this.scene = scene
    this.viewport = viewport
 
    const geometry = new Plane(this.gl)
    const program = new Program(this.gl, {
      vertex,
      fragment,
      uniforms: {
        uColor: { value: new Color('#c4c3b6') }
      },
      transparent: true
    })
 
    this.meshes = []
 
    for (let i = 0; i < 50; i++) {
      let mesh = new Mesh(this.gl, {
        geometry,
        program,
      })
 
      const scale = random(0.75, 1)
 
      mesh.scale.x = 1.6 * scale
      mesh.scale.y = 0.9 * scale
 
      mesh.speed = random(0.75, 1)
 
      mesh.xExtra = 0
 
      mesh.x = mesh.position.x = random(-this.viewport.width * 0.5, this.viewport.width * 0.5)
      mesh.y = mesh.position.y = random(-this.viewport.height * 0.5, this.viewport.height * 0.5)
 
      this.meshes.push(mesh)
 
      this.scene.addChild(mesh)
    }
  }
}

Then after that we just need to apply endless scrolling logic on them as well, following the same directional validation we have in the Media class:

update (scroll, direction) {
  this.meshes.forEach(mesh => {
    mesh.position.x = mesh.x - scroll.current * mesh.speed - mesh.xExtra
 
    const viewportOffset = this.viewport.width * 0.5
    const widthTotal = this.viewport.width + mesh.scale.x
 
    mesh.isBefore = mesh.position.x < -viewportOffset
    mesh.isAfter = mesh.position.x > viewportOffset
 
    if (direction === 'right' && mesh.isBefore) {
      mesh.xExtra -= widthTotal
 
      mesh.isBefore = false
      mesh.isAfter = false
    }
 
    if (direction === 'left' && mesh.isAfter) {
      mesh.xExtra += widthTotal
 
      mesh.isBefore = false
      mesh.isAfter = false
    }
 
    mesh.position.y += 0.05 * mesh.speed
 
    if (mesh.position.y > this.viewport.height * 0.5 + mesh.scale.y) {
      mesh.position.y -= this.viewport.height + mesh.scale.y
    }
  })
}

That’s simple as that, now we have the blocks in the background as well, finalizing the code of our demo!

I hope this tutorial was useful to you and don’t forget to comment if you have any questions!

The post Creating an Infinite Circular Gallery using WebGL with OGL and GLSL Shaders appeared first on Codrops.

Desktop_Application

hi i have to create a desktop application for "Timetabel Management System" . for that i'm gonna use Java language and i'm really confusing about what platform i have to use it? bcaz my group members suggest me eclips and some suggest me Netbeans? can u help me out with some example links? caz i didn't know how to create an desktop application. caz i didn't create one before.