Welcome to an incredibly controversial topic in the land of front-end development! I’m sure that a majority of you reading this have encountered your fair share of #hotdrama surrounding how CSS should be handled within a JavaScript application.
I want to preface this post with a disclaimer: There is no hard and fast rule that establishes one method of handling CSS in a React, or Vue, or Angular application as superior. Every project is different, and every method has its merits! That may seem ambiguous, but what I do know is that the development community we exist in is full of people who are continuously seeking new information, and looking to push the web forward in meaningful ways.
Preconceived notions about this topic aside, let’s take a look at the fascinating world of CSS architecture!
Let us count the ways
Simply Googling “How to add CSS to [insert framework here]” yields a flurry of strongly held beliefs and opinions about how styles should be applied to a project. To try to help cut through some of the noise, let’s examine a few of the more commonly utilized methods at a high level, along with their purpose.
Option 1: A dang ol’ stylesheet
We’ll start off with what is a familiar approach: a dang ol’ stylesheet. We absolutely are able to to an external stylesheet within our application and call it a day.
<link rel="stylesheet" href="styles.css">
We can write normal CSS that we’re used to and move on with our lives. Nothing wrong with that at all, but as an application gets larger, and more complex, it becomes harder and harder to maintain a single stylesheet. Parsing thousands of lines of CSS that are responsible for styling your entire application becomes a pain for any developer working on the project. The cascade is also a beautiful thing, but it also becomes tough to manage in the sense that some styles you — or other devs on the project — write will introduce regressions into other parts of the application. We’ve experienced these issues before, and things like Sass (and PostCSS more recently) have been introduced to help us handle these issues
We could continue down this path and utilize the awesome power of PostCSS to write very modular CSS partials that are strung together via @import rules. This requires a little bit of work within a webpack config to be properly set up, but something you can handle for sure!
No matter what compiler you decide to use (or not use) at the end of the day, you’ll be serving one CSS file that houses all of the styles for your application via a tag in the header. Depending on the complexity of that application, this file has the potential to get pretty bloated, hard to load asynchronously, and render-blocking for the rest of your application. (Sure, render-blocking isn’t always a bad thing, but for all intents and purposes, we’ll generalize a bit here and avoid render blocking scripts and styles wherever we can.)
That’s not to say that this method doesn’t have its merits. For a small application, or an application built by a team with less of a focus on the front end, a single stylesheet may be a good call. It provides clear separation between business logic and application styles, and because it’s not generated by our application, is fully within our control to ensure exactly what we write is exactly what is output. Additionally, a single CSS file is fairly easy for the browser to cache, so that returning users don’t have to re-download the entire file on their next visit.
But let’s say that we’re looking for a bit more of a robust CSS architecture that allows us to leverage the power of tooling. Something to help us manage an application that requires a bit more of a nuanced approach. Enter CSS Modules.
Option 2: CSS Modules
One fairly large problem within a single stylesheet is the risk of regression. Writing CSS that utilizes a fairly non-specific selector could end up altering another component in a completely different area of your application. This is where an approach called “scoped styles” comes in handy.
Scoped styles allow us to programmatically generate class names specific to a component. Thus scoping those styles to that specific component, ensuring that their class names will be unique. This leads to auto-generated class names like header__2lexd. The bit at the end is a hashed selector that is unique, so even if you had another component named header, you could apply a header class to it, and our scoped styles would generate a new hashed suffix like so: header__15qy_.
CSS Modules offer ways, depending on implementation, to control the generated class name, but I’ll leave that up to the CSS Modules documentation to cover that.
Once all is said and done, we are still generating a single CSS file that is delivered to the browser via a tag in the header. This comes with the same potential drawbacks (render blocking, file size bloat, etc.) and some of the benefits (caching, mostly) that we covered above. But this method, because of its purpose of scoping styles, comes with another caveat: the removal of the global scope — at least initially.
Imagine you want to employ the use of a .screen-reader-text global class that can be applied to any component within your application. If using CSS Modules, you’d have to reach for the :global pseudo selector that explicitly defines the CSS within it as something that is allowed to be globally accessed by other components in the app. As long as you import the stylesheet that includes your :global declaration block into your component’s stylesheet, you’ll have the use of that global selector. Not a huge drawback, but something that takes getting used to.
Here’s an example of the :global pseudo selector in action:
You may run the risk of dropping a whole bunch of global selectors for typography, forms, and just general elements that most sites have into one single :global selector. Luckily, through the magic of things like PostCSS Nested or Sass, you can import partials directly into the selector to make things a bit more clean:
This way, you can write your partials without the :global selector, and just import them directly into your main stylesheet.
Another bit that takes some getting used to is how class names are referenced within DOM nodes. I’ll let the individual docs for Vue, React, and Angular speak for themselves there. I’ll also leave you with a little example of what those class references look like utilized within a React component:
The CSS Modules method, again, has some great use cases. For applications looking to take advantage of scoped styles while maintaining the performance benefits of a static, but compiled stylesheet, then CSS Modules may be the right fit for you!
It’s worth noting here as well that CSS Modules can be combined with your favorite flavor of CSS preprocessing. Sass, Less, PostCSS, etc. are all able to be integrated into the build process utilizing CSS Modules.
But let’s say your application could benefit from being included within your JavaScript. Perhaps gaining access to the various states of your components, and reacting based off of the changing state, would be beneficial as well. Let’s say you want to easily incorporate critical CSS into your application as well! Enter CSS-in-JS.
Option 3: CSS-in-JS
CSS-in-JS is a fairly broad topic. There are several packages that work to make writing CSS-in-JS as painless as possible. Frameworks like JSS, Emotion, and Styled Components are just a few of the many packages that comprise this topic.
As a broad strokes explanation for most of these frameworks, CSS-in-JS is largely operates the same way. You write CSS associated with your individual component and your build process compiles the application. When this happens, most CSS-in-JS frameworks will output the associated CSS of only the components that are rendered on the page at any given time. CSS-in-JS frameworks do this by outputting that CSS within a tag in the of your application. This gives you a critical CSS loading strategy out of the box! Additionally, much like CSS Modules, the styles are scoped, and the class names are hashed.
As you navigate around your application, the components that are unmounted will have their styles removed from the and your incoming components that are mounted will have their styles appended in their place. This provides opportunity for performance benefits on your application. It removes an HTTP request, it is not render blocking, and it ensures that your users are only downloading what they need to view the page at any given time.
Another interesting opportunity CSS-in-JS provides is the ability to reference various component states and functions in order to render different CSS. This could be as simple as replicating a class toggle based on some state change, or be as complex as something like theming.
Because CSS-in-JS is a fairly #hotdrama topic, I realized that there are a lot of different ways that folks are trying to go about this. Now, I share the feelings of many other people who hold CSS in high regard, especially when it comes to leveraging JavaScript to write CSS. My initial reactions to this approach were fairly negative. I did not like the idea of cross-contaminating the two. But I wanted to keep an open mind. Let’s look at some of the features that we as front-end-focused developers would need in order to even consider this approach.
If we’re writing CSS-in-JS we have to write real CSS. Several packages offer ways to write template-literal CSS, but require you to camel-case your properties — i.e. padding-left becomes paddingLeft. That’s not something I’m personally willing to sacrifice.
Some CSS-in-JS solutions require you to write your styles inline on the element you’re attempting to style. The syntax for that, especially within complex components, starts to get very hectic, and again is not something I’m willing to sacrifice.
The use of CSS-in-JS has to provide me with powerful tools that are otherwise super difficult to accomplish with CSS Modules or a dang ol’ stylesheet.
We have to be able to leverage forward-thinking CSS like nesting and variables. We also have to be able to incorporate things like Autoprefixer, and other add-ons to enhance the developer experience.
It’s a lot to ask of a framework, but for those of us who have spent most of our lives studying and implementing solutions around a language that we love, we have to make sure that we’re able to continue writing that same language as best we can.
Here’s a quick peek at what a React component using Styled Components could look like:
We also need to address the potential downsides of a CSS-in-JS solution — and definitely not as an attempt to spark more drama. With a method like this, it’s incredibly easy for us to fall into a trap that leads us to a bloated JavaScript file with potentially hundreds of lines of CSS — and that all comes before the developer will even see any of the component’s methods or its HTML structure. We can, however, look at this as an opportunity to very closely examine how and why we are building components the way they are. In thinking a bit more deeply about this, we can potentially use it to our advantage and write leaner code, with more reusable components.
Additionally, this method absolutely blurs the line between business logic and application styles. However, with a well-documented and well-thought architecture, other developers on the project can be eased into this idea without feeling overwhelmed.
TL;DR
There are several ways to handle the beast that is CSS architecture on any project and do so while using any framework. The fact that we, as developers, have so many choices is both super exciting, and incredibly overwhelming. However, the overarching theme that I think continues to get lost in super short social media conversations that we end up having, is that each solution has its own merits, and its own inefficiencies. It’s all about how we carefully and thoughtfully implement a system that makes our future selves, and/or other developers who may touch the code, thank us for taking the time to establish that structure.
Which will behave just like a child element mostly. One tricky thing is that no selector selects it other than the one you used to create it (or a similar selector that is literally a ::before or ::after that ends up in the same place).
To illustrate, say I set up that container to be a 2×2 grid and make each item a kind of pillbox design:
Without the pseudo-element, that would be like this:
If I add that pseudo-element selector as above, I’d get this:
It makes sense, but it can also come as a surprise. Pseudo-elements are often decorative (they should pretty much only be decorative), so having it participate in a content grid just feels weird.
Notice that the .container > * selector didn’t pick it up and make it darkgray because you can’t select a pseudo-element that way. That’s another minor gotcha.
In my day-to-day, I find pseudo-elements are typically absolutely-positioned to do something decorative — so, if you had:
…you probably wouldn’t even notice. Technically, the pseudo-element is still a child, so it’s still in there doing its thing, but isn’t participating in the grid. This isn’t unique to CSS Grid either. For instance, you’ll find by using flexbox that your pseudo-element becomes a flex item. You’re free to float your pseudo-element or do any other sort of layout with it as well.
DevTools makes it fairly clear that it is in the DOM like a child element:
There are a couple more gotchas!
One is :nth-child(). You’d think that if pseduo-elements are actually children, they would effect :nth-child() calculations, but they don’t. That means doing something like this:
.container > :nth-child(2) {
background: red;
}
…is going to select the same element whether or not there is a ::before pseudo-element or not. The same is true for ::after and :nth-last-child and friends. That’s why I put “kinda” in the title. If pseudo-elements were exactly like child elements, they would affect these selectors.
Another gotcha is that you can’t select a pseudo-element in JavaScript like you could a regular child element. document.querySelector(".container::before"); is going to return null. If the reason you are trying to get your hands on the pseudo-element in JavaScript is to see its styles, you can do that with a little CSSOM magic:
And now it’s July. I’d just like to tell my American and Canadian friends to hydrate properly, and the hangover should be gone by October, just in time for the sugar crash. While you recover, we’ve got more portfolios.
Lots of good, grid-based stuff this month. And lots of dark layouts. It’s like all the color got used up last month or something. Anyway, I hope you like minimalism! Enjoy.
Note: I’m judging these sites by how good they look to me. If they’re creative and original, or classic but really well-done, it’s all good to me. Sometimes, UX and accessibility suffer. For example, many of these sites depend on JavaScript to display their content at all; this is a Bad Idea, kids. If you find an idea you like and want to adapt to your own site, remember to implement it responsibly.
White Elephant
White Elephant combines pastels, some asymmetrical design, some very grid-focused design, and a whole lot of absolutely gorgeous type to create a pleasant, pretty browsing experience. Also, there’s the occasional witty GIF used during page transitions, a sort of blink-and-you’ll miss it detail that amused me.
Platform: WordPress
Adam Brandon
Adam Brandon’s portfolio keeps things very minimalist. But then, when you have Netflix, Apple, Nike, and Ford in your portfolio, do you really want that much getting in the way of all those logos? No, in these situations, minimalism is definitely the way to go, and the sort of grid-based collage doesn’t hurt.
Platform: Squarespace
Planetary
Planetary’s agency site is pretty, modern, and I have to say that the designers sure know how to effectively use just the tiniest splashes of extra color for emphasis. It’s just that nice to look at. Plus, this site just introduced me to the typeface known as Neutrif pro, and I think I’m in love.
Platform: Gatsby
Werlen Meyer
Werlen Meyer keeps things fairly simple with a dark layout, simple sans-serif type, and quite a bit of background video. I have to hand it to whomever is optimizing this site. All of the video/animation loaded fast and smooth on my 5Mbs connection.
Platform: Custom CMS (I think. I never know for sure when Lua is listed as the programming language.)
Renaud Rohlinger
Renaud Rohlinger is a “French Creative Developer living in Japan (soon)”, and this is presumably why his site is a mix of Japanese and English. Not content with that, however, he threw in a whole lot of gorgeous WebGL animation that runs pretty smooth and fast, even on my old laptop with integrated graphics.
While there could be some improvements made layout-wise, and the body text could be a little bigger, the whole thing is a technical marvel.
Platform: Static Site
Tiago Majuelos
Tiago Majuelos’ one-page portfolio is colorful, wild, and full of… hieroglyphic-clipart graphics? I’m not sure how the heck else to describe his art style. You can browse the work by dragging/swiping horizontally, or you can scroll down to see the most organized collage I’ve ever seen. I’ve never seen anything quite like this, so it’s on the list.
Platform: WordPress
Josh Newton
Josh Newton’s portfolio has a beautifully simple dark layout, great condensed type, and body text that’s sadly way too small. That usability issue aside, it’s rather nice to look at. Another great grid-based layout.
Platform: Custom CMS (Probably)
Diogo Akio
Diogo Akio’s portfolio is more grid-based goodness, mixed with more horizontal scrolling goodness, and a heavy dose of modern minimalism. And for variety, it’s not a dark layout.
Platform: Static Site
Rise
When Rise was being designed someone clearly looked at a bunch of sites with asymmetry and overlapping elements, and said, “That. I want loads of that.” Then threw in some pretty decent typography, and they’ve managed to pull off using the color yellow, so they’ve earned their spot on this list.
Platform: ExpressionEngine (Haven’t seen that for a while.)
makespace.
Makespace. More dark layout. More collages. More grid stuff. Lots of periods going around. No but really, it’s a fantastic looking site, and you don’t see too many collages that are animated like a slideshow. Unfortunately, this site falls into the same small-text trap that many dark sites fall into in their quest to look elegant.
Elegant text is text I can read. Just saying.
Platform: Static Site
Diana Costa
At first glance, Diana Costa’s portfolio seems downright brutalist. Dig in a little, though, and you’ll see the monochromatic tones and monospaced text tempered with plenty of white space, and simple, smooth animation. It’s amazing how much white space can transform the feel of a website, almost on its own.
Though I don’t like sites that depend on JS to run at all, especially sites this simple, I have to respect the developers for putting in a warning message when you turn JS off.
Platform: Static Site
Firstborn
Like many minimalist sites, Firstborn uses animation to spice things up, decent type, and multicolumn body text. Well okay, multicolumn isn’t that common. No, they’re not relying on CSS to do that just yet, it’s being done with JS. Even so, I’m glad to see it out in the wild.
Platform: Contentful
Yuen Ye
Okay, you know that see-the-preview-on-project-title-hover thing? Well Yuen Ye’s portfolio has officially done it better than everything else. There are elements that are rather convincingly animated to look like cloth on the home page, and hovering over a project title changes the “pieces of cloth” into the preview for… go just got look at it.
Rarely am I actually impressed by these sorts of bells and whistles, but it’s beautifully executed here. The rest of the site is pretty solid, too.
Platform: WordPress
Onix Design
Onix Design improves on the ever-familiar modern/dark aesthetic by adding a fair amount of illustration, including some sort of semi-3D illustrations of faces that I found strangely compelling. The color scheme is a bit of a blast to the eyeballs, even with the mostly-dark layout, but it’s certainly a striking, and uncommon look.
Platform: Custom CMS? (It’s programmed in Go.)
Zef
Zef’s Website is decidedly new-looking, even as certain aspects of it (including the name) evoke a sort of Geocities-era nostalgia. I love everything: the curving text on the home page, the clashing colors, the careless-seeming typography that is actually quite effective. It’s amazing.
Platform: Static Site
thePXA Creative
thePXA Creative’s agency site is darned good-looking. Far more good-looking, perhaps, than usable. Even so, I found myself liking this highly PowerPoint-ish site for it’s sheer style, and commitment to a theme.
Platform: Static Site
Muax
Muax is another dark, grid-loving layout, sure. It’s also a showcase of just how absolutely gorgeous Russian typography can be. I have no idea what any of it says, but I could stare at it for a good ten minutes.
If this is what Dostoevsky’s work looked like in Russian, no wonder people read it to the end.
Platform: Custom CMS
Casper Fitzhue
Casper Fitzhue’s portfolio is one of those very white designs that could get boring if you don’t love that sort of simplicity as much as I do. But to keep things interesting, the background will change color depending on what you’re looking at/hovering over. Plus, I like how things like essays and other text-based works are included in the portfolio.
Platform: Static Site
Chris Cyran
Chris Cyran’s portfolio is maybe the only one I’ve seen that’s bold enough to just sort of… put a business card design right in the middle of the page, and leave it there no matter what else happens to be scrolling by.
Platform: WordPress
Locomotive
Locomotive looks like a site that should be minimalist, but they occasionally just sort of… don’t do minimalist things. It also leans hard into the animation, creating an experience that is at once familiar and outstanding.
As mentioned in part 1, this tutorial is intended for people who know and use the Sketch app and are not afraid of dabbling with code as well. To profit from it the most, you will need to have at least some basic experience writing JavaScript (and, optionally, HTML/CSS).
In the previous part of this tutorial, we learned about the basic files that make up a plugin, and how to create the plugin’s user interface. In this second and final part, we’ll learn how to connect the user interface to the core plugin code and how to implement the plugin’s main features. Last but not least, we’ll also learn how to optimize the code and the way the plugin works.
Building The Plugin’s User Interface: Making Our Web Interface And The Sketch Plugin Code “Talk” To Each Other
The next thing we need to do is to set up communication between our web interface and the Sketch plugin.
We need to be able to send a message from our web interface to the Sketch plugin when the “Apply” button in our web interface is clicked. This message needs to tell us about what settings the user has input — like the number of steps, rotation amount, the number of duplicates to create, and so on.
WKWebView makes this task a little bit easier for us: we can send messages to our Sketch plugin from our web interface’s JavaScript code by using the window.webkit.messageHandlers API.
On our Sketch code’s side, we can use another method, addScriptMessageHandler:name: (or addScriptMessageHandler_name) to register a message handler that will be called whenever it receives a message sent from our plugin web interface.
Let’s start by making sure we can receive messages from our web UI. Head over to our ui.js file’s createWebView function, and add the following:
function createWebView(pageURL){
const webView = WKWebView.alloc().init();
// Set handler for messages from script
const userContentController = webView.configuration().userContentController();
const ourMessageHandler = ...
userContentController.addScriptMessageHandler_name(
ourMessageHandler, "sketchPlugin"
);
// Load page into web view
webView.loadFileURL_allowingReadAccessToURL(pageURL, pageURL.URLByDeletingLastPathComponent());
return webView;
};
Here we use the web view’s userContentController property to add a message handler we’ve named “sketchPlugin”. This “user content controller” is the bridge that ensures messages get across from our web view.
You might have noticed something odd about the above code: the object we’re adding as the message handler, ourMessageHandler, doesn’t exist yet! Unfortunately, we can’t just use a regular JavaScript object or function as the handler, as this method expects a certain kind of native object.
Luckily for us, we can get around this limitation by using MochaJSDelegate, a mini-library I wrote that makes it possible to create the kind of native object we need using regular ol’ JavaScript. You’ll need to manually download and save it in your plugin bundle under Sketch/MochaJSDelegate.js.
In order to use it, we’ll need to first import it into ui.js. Add the following at the top of the file:
Now we can use MochaJSDelegate to create the type of message handler addScriptMessageHandler:name: is expecting:
function createWebView(pageURL){
const webView = WKWebView.alloc().init();
// Set handler for messages from script
const userContentController = webView.configuration().userContentController();
const scriptMessageHandler = new MochaJSDelegate({
"userContentController:didReceiveScriptMessage:": (_, wkMessage) => {
/* handle message here */
}
}).getClassInstance();
userContentController.addScriptMessageHandler_name(
scriptMessageHandler, "sketchPlugin"
);
// Load page into web view
webView.loadFileURL_allowingReadAccessToURL(pageURL, pageURL.URLByDeletingLastPathComponent());
return webView;
};
The code we just added creates the native object we need. It also defines a method on that object named userContentController:didReceiveScriptMessage: — this method is then called with the message we want as the second argument. Since we’re not actually sending any messages yet, we’ll have to come back here at a later time and add some code to actually parse and handle the messages we receive.
Next, we need to add some code to our web interface to send us those messages. Head over to /Resources/web-ui/script.js. You’ll find I’ve already written most of the code that handles retrieving the values of the HTML the user will enter their options into.
What’s still left for us to do is to add the code that actually sends the values over to our Sketch code:
Find the apply function and add the following to the end of it:
// Send user inputs to sketch plugin
window.webkit.messageHandlers.sketchPlugin.postMessage(JSON.stringify({
stepCount, startingOptions, stepOptions
}));
Here we use window.webkit.messageHandlers API we mentioned earlier to access the message handler we registered above as sketchPlugin. Then send a message to it with a JSON string containing the user’s inputs.
Let’s make sure everything is set up properly. Head back to /Sketch/ui.js. In order to make sure we’re getting messages as we expect, we’ll modify the method we defined earlier so that it displays a dialog when we get a message:
Now run the plugin (you may need to first close any existing Mosaic window you have opened), enter some values, then click “Apply”. You should see an alert like the one below — this means everything is wired up correctly and our message went through successfully! If not, go back over the previous steps and ensure that everything was done as described.
Now that we’re able to send messages from our interface to our plugin, we can move on to writing the code that actually does something useful with that information: generating our layer mosaics.
Generating The Layer Mosaics
Let’s take stock of what’s necessary in order to make this happen. Simplifying things a bit, what our code needs to do is:
Find the current document.
Find the current document’s selected layer.
Duplicate the selected layer (we’ll call it the template layer) x number of times.
For each duplicate, tweak its position, rotation, opacity, etc., by the specific values (amounts) set by the user.
Now that we’ve got a reasonable plan, let’s continue writing. Sticking with our pattern of modularizing our code, let’s create a new file, mosaic.js in the Sketch/ folder, and add to it the following code:
function mosaic(options){
};
module.export = mosaic;
We’ll use this function as the only export of this module since it makes for a simpler API to use once we import it — we can just call mosaic() with whatever options we get from the web interface.
The first two steps we need to take are getting the current document, and then its selected layer. The Sketch API has a built-in library for document manipulation which we can get access to by importing the sketch/dom module. We only need the Document object right now, so we’ll pull it out explicitly. At the top of the file, add:
const { Document } = require("sketch/dom");
The Document object has a method specifically for accessing the current document we can use, called getSelectedDocument(). Once we have the current document instance, we can access whatever layers the user has selected via the document’s selectedLayers property. In our case, though, we only care about single-layer selections, so we’ll only grab the first layer the user has selected:
Note:You might have been expecting selectedLayers itself to be an array, but it’s not. Instead, it’s an instance of the Selection class. There’s a reason for this: the Selection class contains a bunch of useful helper methods for manipulating the selection like clear, map, reduce, and forEach. It exposes the actual layer array via the layer property.
Let’s also add some warning feedback in case the user forgets to open a document or to select something:
Now that we’ve written the code for steps 1 and 2 (finding the current document and selected layer), we need to address steps 3 and 4:
Duplicate the template layer x number of times.
For each duplicate, tweak its position, rotation, opacity, etc., by the specific values set by the user.
Let’s start by pulling all the relevant information we need out of options: the number of times to duplicate, starting options, and step options. We can once again use destructuring (like we did earlier with Document) to pull those properties out of options:
function mosaic(options) {
// ...
// Destructure options:
var { stepCount, startingOptions, stepOptions } = options;
}
Next, let’s sanitize our inputs and ensure that step count is always at least 1:
Now we need to make sure that the template layer’s opacity, rotation, etc., all match up with the user’s desired starting values. Since applying the user’s options to a layer is going to be something we’ll do a lot of, we’ll move this work into its own method:
And because spacing only needs to be applied in-between the duplicates and not the template layer, we’ve added a specific flag, shouldAdjustSpacing, that we can set to true or false depending on whether we’re applying options to a template layer or not. That way we can ensure that rotation and opacity will be applied to the template, but not spacing.
Back in the mosaic method, let’s now ensure that the starting options are applied to the template layer:
function mosaic(options){
// ...
// Configure template layer
var layer = group.layers[0];
configureLayer(layer, startingOptions, false);
}
Next, we need to create our duplicates. First, let’s create a variable that we can use to track what the options for the current duplicate are:
function mosaic(options){
// ...
var currentOptions; // ...
}
Since we already applied the starting options to the template layer, we need to take those options we just applied and add the relative values of stepOptions in order to get the options to apply to the next layer. Since we’ll also be doing this several more times in our loop, we’ll also move this work into a specific method, stepOptionsBy:
function stepOptionsBy(start, step){
const newOptions = {};
for(let key in start){
newOptions[key] = start[key] + step[key];
}
return newOptions;
};
After that, we need to write a loop that duplicates the previous layer, applies the current options to it, then offsets (or “steps”) the current options in order to get the options for the next duplicate:
function mosaic(options) {
// ...
var currentOptions = stepOptionsBy(startingOptions, stepOptions);
for(let i = 0; i
All done — we’ve successfully written the core of what our plugin is supposed to do! Now, we need to wire things up so that when the user actually clicks the “Apply” button our mosaic code is invoked.
Let’s head back to ui.js and adjust our message handling code. What we’ll need to do is parse the JSON string of options we’re getting so that they’re turned into an object we can actually use. Once we have these options, we can then call the mosaic function with them.
First, parsing. We’ll need to update our message handling function to parse the JSON message we get:
Next, we’ll need to pass this over to our mosaic function. However, this isn’t really something our code in ui.js should be doing — it’s supposed to be primarily concerned with what’s necessary to display interface-related things on screen — not creating mosaics itself. To keep these responsibilities separate, we’ll add a second argument to createWebView that takes a function, and we’ll call that function whenever we receive options from the web interface.
However, if we ran our code now and clicked the “Apply” button in the plugin interface, nothing would happen. Why? The reason is due to how Sketch scripts are run: by default, they “live” only until the bottom of your script is reached, after which Sketch destroys it and frees up whatever resources it was using.
This is a problem for us since it means that anything we need to have asynchronously happen (in this case, that’s after the bottom of our code is reached), like receiving messages, cannot, because our script has been destroyed. This means we wouldn’t get any of our messages from the web interface since we’re not around to receive and respond to them!
There’s a way to signal to Sketch that we need our script to stay alive beyond this point, using Fibers. By creating a Fiber, we tell Sketch that something asynchronous is happening and that it needs to keep our script around. Sketch will then only destroy our script when absolutely necessary (like the user closing Sketch, or when the Mosaic plugin needs to be updated):
Voilà! Let’s try out our plugin now. With a layer selected in Sketch, enter some settings, then click apply:
Final Improvements
Now that we’ve got the majority of our plugin’s functionality implemented, we can try to “zoom out” a bit and take a look at the big picture.
Improving The User’s Experience
If you’ve played around with the plugin in its current state, you might’ve noticed that one of the biggest points of friction appears when you try to edit a Mosaic. Once you create one, you have to hit undo, adjust the options, then click ‘Apply’ (or press Enter). It also makes it harder to edit a Mosaic after you’ve left your document and returned to it later, since your undo/redo history will have been wiped out, leaving you to manually delete the duplicate layers yourself.
In a more ideal flow, the user could just select a Mosaic group, adjust options and watch the Mosaic update until they get the exact arrangement they’re looking for. To implement this, we have two problems to solve:
First, we’ll need a way to group the duplicates that make up a Mosaic together. Sketch provides the concept of Groups, which we can use to solve this problem.
Second, we’ll need a way to tell the difference between a normal, user-created group and a Mosaic group. Sketch’s API also gives us a way to store information on any given layer, which we can use as a way tag and later identify a group as one of our ‘special’ Mosaic groups.
Let’s revisit the logic we wrote in the previous section to address this. Our original code follows the following steps:
Find the current document.
Find the current document’s selected layer.
Duplicate the selected layer (we’ll call it the template layer) x number of times.
For each duplicate, tweak its position, rotation, opacity, etc., by the specific values (amounts) set by the user.
In order to make our new user flow possible, we need to change these steps to:
Grab the current document.
Grab the current document’s selected layer.
Determine whether the selected layer is a Mosaic group or not.
If it’s some other layer, use it as the template layer and go to step 4.
If it is a Mosaic group, consider the first layer in it as the template layer, and go to step 5.
Wrap the template layer inside a group, and mark that group as a Mosaic group.
Remove all layers from inside the group except the template layer.
Duplicate the template layer x number of times.
For each duplicate, tweak its position, rotation, opacity, etc., by the specific values set by the user.
We’ve got three new steps. For the first new step, step 3, we’ll create a function named findOrMakeSpecialGroupIfNeeded that will look at the layer passed to it to determine whether or not it’s a Mosaic group. If it is, we’ll just return it. Since the user could potentially select a sublayer nested deep in a Mosaic group, we’ll also need to check the parents of the selected layer to tell if they’re one of our Mosaic groups as well:
function findOrMakeSpecialGroupIfNeeded(layer){
// Loop up through the parent hierarchy, looking for a special group
var layerToCheck = layer;
while(layerToCheck){
if(/* TODO: is mosaic layer? */){
return layerToCheck;
}
layerToCheck = layerToCheck.parent;
}
};
If we weren’t able to find a Mosaic group we’ll simply wrap the layer we were passed inside a Group, then tag it as a Mosaic group.
Back at the top of the file, we’ll need to pull out the Group class now too:
const { Document, Group } = require("sketch/dom");
function findOrMakeSpecialGroupIfNeeded(layer){
// Loop up through the parent hierarchy, looking for a special group
var layerToCheck = layer;
while(layerToCheck){
if(/* TODO: is mosaic layer? */){
return layerToCheck;
}
layerToCheck = layerToCheck.parent;
}
// Group
const destinationParent = layer.parent;
const group = new Group({
name: "Mosaic Group",
layers: [ layer ],
parent: destinationParent
});
/* TODO: mark group as mosaic layer */
return group;
};
Now we need to fill in the gaps (todo’s). To begin with, we need a means of identifying whether or not a group is one of the special groups belonging to us or not. Here, the Settings module of the Sketch library comes to our rescue. We can use it to store custom information on a particular layer, and also to read it back.
function findOrMakeSpecialGroupIfNeeded(layer){
const isSpecialGroupKey = "is-mosaic-group";
// Loop up through the parent hierarchy, looking for a special group
var layerToCheck = layer;
while(layerToCheck){
let isSpecialGroup = Settings.layerSettingForKey(layerToCheck, isSpecialGroupKey);
if(isSpecialGroup) return layerToCheck;
layerToCheck = layerToCheck.parent;
}
// Group
const destinationParent = layer.parent;
layer.remove(); // explicitly remove layer from it's existing parent before adding it to group
const group = new Group({
name: "Mosaic Group",
layers: [ layer ],
parent: destinationParent
});
Settings.setLayerSettingForKey(group, isSpecialGroupKey, true);
return group;
};
Now that we’ve got a method that handles wrapping a layer in a mosaic group (or, if already a mosaic group, just returns it) we can now plug it into our main mosaic method just after our safety checks:
function mosaic(options){
// ... safety checks ...
// Group selection if needed:
const group = findOrMakeSpecialGroupIfNeeded(selectedLayer);
}
Next we’ll add a loop to remove all layers from the group except the template layer (which is the first):
function mosaic(options) {
// ...
// Remove all layers except the first:
while(group.layers.length > 1){
group.layers[group.layers.length - 1].remove();
}
}
Lastly, we’ll make sure that the group’s size is fitted to its new contents since the user might have originally selected a layer nested within the old group (a layer that we might have removed).
We’ll also need to make sure to set the current selection to our mosaic group itself. This will ensure that if the user is making a bunch of rapid changes to the same mosaic group it won’t become deselected. After the code we already wrote to duplicate a layer, add:
function mosaic(options) {
// ...
// Fit group to duplicates
group.adjustToFit();
// Set selection to the group
document.selectedLayers.clear();
group.selected = true;
}
Try out the plugin again. You should find that editing a mosaic is much smoother now!
Improving The Interface
One other thing you might notice is the lack of synchronization between the display window and the interface inside it, in terms of them both becoming visible at the same time. This is due to the fact that when we display the window, the web interface isn’t guaranteed to have finished loading, so sometimes it’ll “pop” or “flash in” afterwards.
One way to fix this is by listening for when the web interface has finished loading, and only then show our window. There is a method, webView:didFinishNavigation:, that WKWebView will call when the current page has finished loading. We can use it to get exactly the notification we’re looking for.
Back in ui.js, we’ll extend the MochaJSDelegate instance we created to implement this method, which will in turn call the onLoadFinish argument we’ll pass to createWebView:
function createWebView(pageURL, onApplyMessage, onLoadFinish){
const webView = WKWebView.alloc().init();
// Create delegate
const delegate = new MochaJSDelegate({
"webView:didFinishNavigation:": (webView, navigation) => {
onLoadFinish();
},
"userContentController:didReceiveScriptMessage:": (_, wkMessage) => {
const message = JSON.parse(wkMessage.body());
onApplyMessage(message);
}
}).getClassInstance();
// Set load complete handler
webView.navigationDelegate = delegate;
// Set handler for messages from script
const userContentController = webView.configuration().userContentController();
userContentController.addScriptMessageHandler_name(delegate, "sketchPlugin");
// Load page into web view
webView.loadFileURL_allowingReadAccessToURL(pageURL, pageURL.URLByDeletingLastPathComponent());
return webView;
};
And back in the loadAndShow method, we’ll adjust it so that it only shows the window once the web view has loaded:
Bingo! Now our window displays only when the web view has finished loading, avoiding that annoying visual flicker.
Conclusion
Congratulations, you’ve built your first Sketch plugin! ?
If you’d like to install and play around with Mosaic, you can download the complete plugin from GitHub. And before you go, here are a few resources that might be handy during the rest of your journey:
developer.sketchapp.com
The official resource regarding Sketch plugin development. Contains several useful guides, as well as an API reference for the Sketch JavaScript library.
sketchplugins.com
A fantastic and helpful community of Sketch plugin developers. Great for getting all your burning questions answered.
github.com/sketchplugins/plugin-directory
Official, central GitHub repository of Sketch plugins. You can submit your plugins here and share them with the rest of the Sketch community!
Every week users submit a lot of interesting stuff on our sister site Webdesigner News, highlighting great content from around the web that can be of interest to web designers.
The best way to keep track of all the great stories and news being posted is simply to check out the Webdesigner News site, however, in case you missed some here’s a quick and useful compilation of the most popular designer news that we curated from the past week.
Note that this is only a very small selection of the links that were posted, so don’t miss out and subscribe to our newsletter and follow the site daily for all the news.
User Experience: Best Practices on Effective Visual Hierarchy
HTML Can do That?
Ikea Releases Free ‘Soffa Sans’ Font Made of Couches
Motion Icons
Designing To-Do Lists App
Microsoft is Teasing ‘All-new’ Windows 1.0 Launch… For Some Reason
Why Google Duplex Might Make my Design Job Redundant
Welcome to the Old Internet Again
Material Design Data Visualization Guidelines
Best React Open-source Projects
Tips for Rolling your own Lazy Loading
Approaching the Website Design Process from the Browser
Designers Discuss Imposter Syndrome
Track this – A New Kind of Incognito (by Firefox)
Introduction to SVG Filters
Apple is Reportedly Giving up on its Controversial MacBook Keyboard
How a Google Side Project Evolved into a $4B Company
How Google Pagespeed Works
Building a Design System?-?where to Start?
8 Tips for Writing Great Usability Tasks
Design is a (hard) Job.
How Successful Design Companies Market Themselves
What if all your Slack Chats were Leaked?
Dark Patterns at Scale: Findings from a Crawl of 11K Shopping Websites
Designing for the Near Future
Want more? No problem! Keep track of top design news from around the web with Webdesigner News.
<!-- Draw the star as a symbol and remove it from view -->
<svg xmlns="http://www.w3.org/2000/svg" >
<symbol id="star" viewBox="214.7 0 182.6 792">
<!-- <path>s and whatever other shapes in here -->
</symbol>
</svg>
<!-- Then use anywhere and as many times as we want! -->
<svg class="icon">
<use xlink:href="#star" />
</svg>
<svg class="icon">
<use xlink:href="#star" />
</svg>
<svg class="icon">
<use xlink:href="#star" />
</svg>
<svg class="icon">
<use xlink:href="#star" />
</svg>
<svg class="icon">
<use xlink:href="#star" />
</svg>
What are the benefits? Well, we’re talking zero requests, cleaner HTML, no worries about pixelation, and accessible attributes right out of the box. Plus, we’ve got the flexibility to use the stars anywhere and the scale to use them as many times as we want with no additional penalties on performance. Score!
The ultimate benefit is that this doesn’t require additional overhead, either. For example, we don’t need a build process to make this happen and there’s no reliance on additional image editing software to make further changes down the road (though, let’s be honest, it does help).
This method is very similar to background-image method, though improves on it by optimizing drawing the shape with CSS properties rather than making a call for an image. We might think of CSS as styling elements with borders, fonts and other stuff, but it’s capable of producing ome pretty complex artwork as well. Just look at Diana Smith’s now-famous “Francine” portrait.
In the world of likes and social statistics, reviews are very important method for leaving feedback. Users often like to know the opinions of others before deciding on items to purchase themselves, or even articles to read, movies to see, or restaurants to dine.
Developers often struggle with with reviews — it is common to see inaccessible and over-complicated implementations. Hey, CSS-Tricks has a snippet for one that’s now bordering on a decade.
Let’s walk through new, accessible and maintainable approaches for this classic design pattern. Our goal will be to define the requirements and then take a journey on the thought-process and considerations for how to implement them.
Scoping the work
Did you know that using stars as a rating dates all the way back to 1844 when they were first used to rate restaurants in Murray’s Handbooks for Travellers — and later popularized by Michelin Guides in 1931 as a three-star system? There’s a lot of history there, so no wonder it’s something we’re used to seeing!
There are a couple of good reasons why they’ve stood the test of time:
Clear visuals (in the form of five hollow or filled stars in a row)
A straightforward label (that provides an accessible description, like aria-label)
When we implement it on the web, it is important that we focus meeting both of those outcomes.
It is also important to implement features like this in the most versatile way possible. That means we should reach for HTML and CSS as much as possible and try to avoid JavaScript where we can. And that’s because:
JavaScript solutions will always differ per framework. Patterns that are typical in vanilla JavaScript might be anti-patterns in frameworks (e.g. React prohibits direct document manipulation).
Languages like JavaScript evolve fast, which is great for community, but not so great articles like this. We want a solution that’s maintainable and relevant for the long haul, so we should base our decisions on consistent, stable tooling.
Methods for creating the visuals
One of the many wonderful things about CSS is that there are often many ways to write the same thing. Well, the same thing goes for how we can tackle drawing stars. There are five options that I see:
Which one to choose? It depends. Let’s check them all out.
Method 1: Using an image file
Using images means creating elements — at least 5 of them to be exact. Even if we’re calling the same image file for each star in a five-star rating, that’s five total requests. What are the consequences of that?
More DOM nodes make document structure more complex, which could cause a slower page paint. The elements themselves need to render as well, which means either the server response time (if SSR) or the main thread generation (if we’re working in a SPA) has to increase. That doesn’t even account for the rendering logic that has to be implemented.
It does not handle fractional ratings, say 2.3 stars out of 5. That would require a second group of duplicated elements masked with clip-path on top of them. This increases the document’s complexity by a minimum of seven more DOM nodes, and potentially tens of additional CSS property declarations.
Optimized performance ought to consider how images are loaded and implementing something like lazy-loading) for off-screen images becomes increasingly harder when repeated elements like this are added to the mix.
It makes a request, which means that caching TTLs should be configured in order to achieve an instantaneous second image load. However, even if this is configured correctly, the first load will still suffer because TTFB awaits from the server. Prefetch, pre-connect techniques or the service-worker should be considered in order to optimize the first load of the image.
It creates minimum of five non-meaningful elements for a screen reader. As we discussed earlier, the label is more important than the image itself. There is no reason to leave them in the DOM because they add no meaning to the rating — they are just a common visual.
The images might be a part of manageable media, which means content managers will be able to change the star appearance at any time, even if it’s incorrect.
It allows for a versatile appearance of the star, however the active state might only be similar to the initial state. It’s not possible to change the image src attribute without JavaScript and that’s something we’re trying to avoid.
Wondering how the HTML structure might look? Probably something like this:
<div class="Rating" aria-label="Rating of this item is 3 out of 5">
<img src="/static/assets/star.png" class="Rating--Star Rating--Star__active">
<img src="/static/assets/star.png" class="Rating--Star Rating--Star__active">
<img src="/static/assets/star.png" class="Rating--Star Rating--Star__active">
<img src="/static/assets/star.png" class="Rating--Star">
<img src="/static/assets/star.png" class="Rating--Star">
</div>
In order to change the appearance of those stars, we can use multiple CSS properties. For example:
.Rating--Star {
filter: grayscale(100%); // maybe we want stars to become grey if inactive
opacity: .3; // maybe we want stars to become opaque
}
An additional benefit of this method is that the element is set to inline-block by default, so it takes a little bit less styling to position them in a single line.
This was once a fairly common implementation. That said, it still has its pros and cons.
For example:
Sure, it’s only a single server request which alleviates a lot of caching needs. At the same time, we now have to wait for three additional events before displaying the stars: That would be (1) the CSS to download, (2) the CSSOM to parse, and (3) the image itself to download.
It’s super easy to change the state of a star from empty to filled since all we’re really doing is changing the position of a background image. However, having to crack open an image editor and re-upload the file anytime a change is needed in the actual appearance of the stars is not the most ideal thing as far as maintenance goes.
We can use CSS properties like background-repeat property and clip-path to reduce the number of DOM nodes. We could, in a sense, use a single element to make this work. On the other hand, it’s not great that we don’t technically have good accessible markup to identify the images to screen readers and have the stars be recognized as inputs. Well, not easily.
In my opinion, background images are probably best used complex star appearances where neither CSS not SVG suffice to get the exact styling down. Otherwise, using background images still presents a lot of compromises.
SVG is great! It has a lot of the same custom drawing benefits as raster images but doesn’t require a server call if it’s inlined because, well, it’s simply code!
We could inline five stars into HTML, but we can do better than that, right? Chris has shown us a nice approach that allows us to provide the SVG markup for a single shape as a and call it multiple times with with .
<!-- Draw the star as a symbol and remove it from view -->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="star" viewBox="214.7 0 182.6 792">
<!-- <path>s and whatever other shapes in here -->
</symbol>
</svg>
<!-- Then use anywhere and as many times as we want! -->
<svg class="icon">
<use xlink:href="#star" />
</svg>
<svg class="icon">
<use xlink:href="#star" />
</svg>
<svg class="icon">
<use xlink:href="#star" />
</svg>
<svg class="icon">
<use xlink:href="#star" />
</svg>
<svg class="icon">
<use xlink:href="#star" />
</svg>
What are the benefits? Well, we’re talking zero requests, cleaner HTML, no worries about pixelation, and accessible attributes right out of the box. Plus, we’ve got the flexibility to use the stars anywhere and the scale to use them as many times as we want with no additional penalties on performance. Score!
The ultimate benefit is that this doesn’t require additional overhead, either. For example, we don’t need a build process to make this happen and there’s no reliance on additional image editing software to make further changes down the road (though, let’s be honest, it does help).
This method is very similar to background-image method, though improves on it by optimizing drawing the shape with CSS properties rather than making a call for an image. We might think of CSS as styling elements with borders, fonts and other stuff, but it’s capable of producing ome pretty complex artwork as well. Just look at Diana Smith’s now-famous “Francine” portrait.
We’re not going to get that crazy, but you can see where we’re going with this. In fact, there’s already a nice demo of a CSS star shape right here on CSS-Tricks.
Or, hey, we can get a little more crafty by using the clip-path property to draw a five-point polygon. Even less CSS! But, buyer beware, because your cross-browser support mileage may vary.
This method is very nice, but very limited in terms of appearance. Why? Because the appearance of the star is set in stone as a Unicode character. But, hey, there are variations for a filled star (?) and an empty star (?) which is exactly what we need!
Unicode characters are something you can either copy and paste directly into the HTML:
We can use font, color, width, height, and other properties to size and style things up a bit, but not a whole lot of flexibility here. But this is perhaps the most basic HTML approach of the bunch that it almost seems too obvious.
Instead, we can move the content into the CSS as a pseudo-element. That unleashes additional styling capabilities, including using custom properties to fill the stars fractionally:
Let’s break this last example down a bit more because it winds up taking the best benefits from other methods and splices them into a single solution with very little drawback while meeting all of our requirements.
Let’s start with HTML. there’s a single element that makes no calls to the server while maintaining accessibility:
<div class="stars" style="--rating: 2.3;" aria-label="Rating of this product is 2.3 out of 5."></div>
As you may see, the rating value is passed as an inlined custom CSS property (--rating). This means there is no additional rendering logic required, except for displaying the same rating value in the label for better accessibility.
Let’s take a look at that custom property. It’s actually a conversion from a value value to a percentage that’s handled in the CSS using the calc() function:
--percent: calc(var(--rating) / 5 * 100%);
I chose to go this route because CSS properties — like width and linear-gradient — do not accept values. They accept and instead and have specific units in them, like % and px, em. Initially, the rating value is a float, which is a type. Using this conversion helps ensure we can use the values in a number of ways.
Filling the stars may sound tough, but turns out to be quite simple. We need a linear-gradient background to create hard color stops where the gold-colored fill should end:
Note that I am using custom variables for colors because I want the styles to be easily adjustable. Because custom properties are inherited from the parent elements styles, you can define them once on the :root element and then override in an element wrapper. Here’s what I put in the root:
The last thing I did was clip the background to the shape of the text so that the background gradient takes the shape of the stars. Think of the Unicode stars as stencils that we use to cut out the shape of stars from the background color. Or like a cookie cutters in the shape of stars that are mashed right into the dough:
Of the five methods we covered, two are my favorites: using SVG (Method 3) and using Unicode characters in pseudo-elements (Method 5). There are definitely use cases where a background image makes a lot of sense, but that seems best evaluated case-by-case as opposed to a go-to solution.
You have to always consider all the benefits and downsides of a specific method. This is, in my opinion, is the beauty of front-end development! There are multiple ways to go, and proper experience is required to implement features efficiently.
Now that we’re running this, I’ve got loads of Pull Requests for conferences all around the world. I didn’t realize that many (most?) conferences use the template at confcodeofconduct.com. In fact, many of them just link to it and call it a day.
That’s why I’m very happy to see there is a new, bold warning about doing just that.
Important notice
This code of conduct page is a template and should not be considered as enforceable. If an event has linked to this page, please ask them to publish their own code of conduct including details on how to report issues and where to find support.
It’s great that this site exists to give people some starter language for thinking about the idea of a code of conduct, but I can attest to the fact that many conferences used it as a way to appear to have a code of conduct before this warning while make zero effort to craft their own.
The primary concern about linking directly to someone else’s code of conduct or copy and pasting it to a new page verbatim is that there is nothing about what to do in case of problems. So, should a conduct incident occur, there is no documented information for what people should do in that event. Without actionable follow-through, a code of conduct is close to meaningless. It’s soul-less placating.
This is just one example:
It’s not to single someone out. It’s just one example of at least a dozen.
I heard from quite a few people about this, and I agree that it’s potentially a serious issue. I’ve tried to be clear about it: I won’t merge a Pull Request if the conference is missing a code of conduct or it simply links to confcodeofconduct.com (or uses a direct copy of it with no actionable details).
I know the repo is looking for help translating the new warning into different languages. If you can help with that, I’m sure they’d love a PR to the appropriate index HTML file.
Imagine you want to buy a new phone. You have several interesting models in mind, but you’re not sure which one of them suits your needs in the best way.
How are you going to make a decision?
Of course, you’re going to look them up online. Read specifications. Compare features. Most importantly, you’ll want to see what other users think about different models. You probably wouldn’t want to buy a phone with an endless list of negative reviews. And this precisely is the typical purchasers’ thought flow in a large number of cases.
So, truth be told your potential customers and other people trust online reviews more than they should, especially true when it comes to millennials. Furthermore, the millennials are primarily impacted by negative reviews, which is one of the major concerns for a large number of businesses.
Accordingly, the impact of negative reviews on millennials in the workplace may significantly change your business results. That’s why in this article we’re discussing why negative reviews are so important to millennials, and how you can handle negative reviews to reduce their effects on your business performance.
Why Do Millennials Look For Negative Reviews?
According to the Medallia Institute, three out of four Millennials do thorough research before they make a purchase. Moreover, the reviews impact their purchase decisions three times more than Boomers’ shopping choices.
But what makes them so interested in negative reviews? Well, some of the main reasons for their increased interest in previous bad experiences lies in their skepticism about the way companies treat customers.
On the one hand, research performed by The Drum has shown that the majority of Millennials think that brands put no effort in the creation of an emotional connection with them. That’s why they feel unmotivated to stay loyal and do precisely the opposite, considering negative reviews as reasons to search for another brand, that will appreciate them more.
On the other hand, it’s more than likely that negative reviews will influence the Millennials if they refer specifically to customer service satisfaction. For instance, 66% of millennials consider the speed of social media supports an important factor for building a lasting relationship.
So, it’s not surprising that they try to inform themselves about this business aspect scrolling through the reviews. So, if previous feedback is negative and shows them your support system is weak, they will look for a more customer-oriented brand.
Steps That Can Help Business Overcome Negative Reviews
As stated by Neil Patel, negative reviews can increase your conversion rate, if you deal with them in the right way. So read on, because we’re listing some great tips on how to reduce the damage caused by negative reviews and turn them into an advantage, instead.
#1 Manage online reviews quickly.
Leaving a review unattended for a while will make your website visitors think you don’t care. So, make sure to provide relevant feedback as soon as possible. Giving instant feedback
Even if you cannot solve the issue promptly, you can explain that you’re working on it and ask for some patience. Once everything’s fixed, you can provide the final feedback. But don’t make the customers wait.
#2 Do not use template-based feedback.
The only thing that shows that you don’t care about customers more than an unattended review is general feedback that doesn’t even focus on the pain points presented in the review. Avoid automated, cold, and formal templates that don’t lead anywhere. They may worsen the image of your brand.
#3 Show understanding and empathy.
No matter whether the customer is right or not, the best response to negative reviews is based on a compassionate approach. Don’t make them feel guilty.
Instead, be genuine, polite, and try not to use an argumentative tone. You don’t want the public to read your verbal fight but to show that you care even when it’s not your business’ fault. The fact that a customer is wrong doesn’t mean you are allowed to be condescending.
#4 Use a survey to collect feedback.
There are also some things you can do to prevent negative public feedback. Two most effective ones are:
the use of live chat software
the use of email surveys
With a live chat tool, you can reach out to your customers as soon as they’ve purchased products. You can either ask them to fill in a survey or ask them to tell you if they had any issues during the purchase process. This way, if they have any frustrations, they will share them directly with you, provided that you’ve implemented live chat software. At best, that will be enough for them, and they won’t feel the need for leaving a negative public review.
When it comes to email surveys, they are the ideal way of collecting feedback both when it comes to potential customers that use a trial version of your product and to those who have already bought the product. All you have to do is to ask for their feedback. You can create your set of questions or refer to survey question examples available online. By acting proactively, you may avoid giving them time to leave potentially negative feedback.
Of course, you can also use the data collected by live chat software and email surveys to create customized feedback that you’ll promote publicly. Use the positive segments of feedback provided by customers to make a special review and testimonial section.
#5 Offer compensation for an unpleasant experience.
If you realize that your staff did mess up attending a customer, you should be ready not only to say sorry but also to do something to show you are sorry.
By this, we’re suggesting you offer a gift pack, a discount, or any special condition that will make your customers’ trust you again. Of course, make sure to show your generosity by making a public offer, right under the negative review.
This way, not only will you prevent from losing a specific customer, but you’ll also show the potential new ones that you know how to handle unpleasant situations.
Millennials And Negative Reviews: How To Handle Them?
Long story short, to not make millennials leave owed to negative reviews, you should take care of two crucial aspects:
1. Try not to give them a reason to leave a negative review: provide effective live chat support, follow them through the purchase process, and catch up with them after a while
2. Take care of all reviews in a timely, constructive, and polite manner
What makes the negative review less dangerous for your business performance is the way you reply to it. What you should remember is to avoid using pre-made generic templates. Also, try to turn negative feedback into a challenging marketing space where you will show that you can show compassion and make up for poor service.
With Instagram, Snapchat and other “young” social media platforms taking over the internet, Twitter still remains one of the most popular marketing channels.
It has around 326 million active users per month which means that your target audience is likely using it. This is why you should at least consider using it as a marketing channel for your business. Having Twitter as one of the main marketing channels is not only going to help build awareness of your brand but can also drive traffic to your website and make your articles go viral.
How can this be achieved? Well, it’s a pretty straightforward tactic that some of the top bloggers are using. The idea is that within your blog post you have short pieces of content that are catchy and people like them. Those pieces of content can then be easily tweeted. (For example, it could be a quote from someone who is an authority in your niche, or a statistic that you feel is likely to be shared by your visitors.)
So, let me show you how you can have “click to tweet” buttons added automatically to every quote in your article.
Before we get started, let me say that this is a medium-advanced technique. If you’re using WordPress you can use a ready-to-go plugin that will serve the same purpose.
Creating “Click to Tweet” Buttons with JavaScript
Let’s assume that we want to turn from this article about conferences that says “According to webdesignerdepot.com …” into a tweetable block.
Given that we have the following html structure:
<article id="article">
<blockquote> I only ever go to marketing conferences to meet new people and listen to their experiences. Those people never include the speakers, who are generally wrapped up in their own world and rarely give in the altruistic sense. </blockquote>
</article>
Here is how we would create a click to tweet button:
Step 1
We need to collect all the and
elements from our article when the document loads:
document.addEventListener("DOMContentLoaded", function() {
// Step 1. Get all quote elements inside the article
const articleBody = document.getElementById('article');
const quotes = [...articleBody.querySelectorAll('quote, blockquote')];
Step 2
Create additional variables to store the current page url, tweetable url and a ‘click to tweet’ button:
let tweetableUrl = "";
let clickToTweetBtn = null;
const currentPageUrl = window.location.href;
Step 3
We need to iterate over all the quote elements that we want to make tweetable and append a “click to tweet button” for each of them:
quotes.forEach(function (quote) {
// Create a tweetable url
tweetableUrl = makeTweetableUrl(
quote.innerText, currentPageUrl
);
// Create a 'click to tweet' button with appropriate attributes
clickToTweetBtn = document.createElement("a");
clickToTweetBtn.innerText = "Click to Tweet";
clickToTweetBtn.setAttribute("href", tweetableUrl);
clickToTweetBtn.onclick = onClickToTweet;
// Add button to every blockquote
quote.appendChild(clickToTweetBtn);
});
});
Step 4
Add 2 missing functions: makeTweetableUrl, that will create a tweetable URL, and onClickToTweet that will act as our event listener and open a window for the tweet (once the button is clicked):
Now, let me show you a slightly different way to get the same result that you can implement if you’re using jQuery.
Here is the code:
$(document).ready(function() {
// Get all quote elements inside the article
const articleBody = $("#article");
const quotes = articleBody.find("quote, blockquote");
let tweetableUrl = "";
let clickToTweetBtn = null;
// Get a url of the current page
const currentPageUrl = window.location.href;
quotes.each(function (index, quote) {
const q = $(quote);
// Create a tweetable url
tweetableUrl = makeTweetableUrl(
q.text(), currentPageUrl
);
// Create a 'click to tweet' button with appropriate attributes
clickToTweetBtn = $("<a>");
clickToTweetBtn.text("Click to Tweet");
clickToTweetBtn.attr("href", tweetableUrl);
clickToTweetBtn.on("click", onClickToTweet);
// Add button to every blockquote
q.append(clickToTweetBtn);
});
});
function makeTweetableUrl(text, pageUrl) {
const tweetableText = "https://twitter.com/intent/tweet?url=" + pageUrl + "&text=" + encodeURIComponent(text);
return tweetableText;
}
function onClickToTweet(e) {
e.preventDefault();
window.open(
e.target.getAttribute("href"),
"twitterwindow",
"height=450, width=550, toolbar=0, location=0, menubar=0, directories=0, scrollbars=0"
);
}
As you can see, the code is the same, except that it takes advantage of jQuery’s functions to simplify the code we have to write. Here’s the jQuery version working on CodePen.
Conclusion
As you can see, creating a “click to tweet” button doesn’t require much time but it can be a great way to encourage your visitors to share your content on Twitter.
I hope this tutorial was helpful and you learned something new. Feel free to use this piece of code and implement it on your website.
This tutorial is intended for people who know and use the Sketch app and are not afraid of dabbling with code. To profit from it the most, you will need to have at least some basic experience writing JavaScript (and, optionally, HTML/CSS).
The plugin we’ll be creating is called “Mosaic”. In part one, we’ll learn about the basic files that make up a Sketch plugin; we’ll write some JavaScript and create a user interface for our plugin with the help of some HTML and CSS. The next article will be about how to connect the user interface to the core plugin code, how to implement the plugin’s main features, and at the end of it, you will also learn how to optimize the code and the way the plugin works.
I’ll also be sharing the plugin’s code (JS, HTML, CSS) and files which you’ll be able to examine and use for learning purposes.
What Are Sketch Plugins, And How Do They Work?
In Sketch, plugins are a way to add features and functionality that aren’t present in Sketch “out of the box.” Considering that there’s almost always going to be some missing feature or integration in any given program (especially given the vast number of needs any individual designer might have!), one can begin to imagine how plugins might be especially useful and powerful. Sketch plugins are able to do pretty much everything you’d expect, like manipulating the color, shape, size, order, style, grouping, and effects of layers, but also able to do things like make requests to internet resources, present a user interface, and much, much more!
On the programming side, all Sketch plugins are written in JavaScript code. Well, actually, that’s not entirely true. It’s more accurate to say that most Sketch plugins are written in JavaScript, as it’s also possible to write a Sketch plugin in one of Apple’s programming languages, Objective-C and Swift, though even they require a small amount of JavaScript knowledge.
Don’t worry though. In this article, we’ll focus on how to build Sketch plugins using JavaScript, HTML, and CSS alone. We won’t be going over the basics of HTML, CSS, or JavaScript — this article assumes at least some knowledge and experience with all of these three. The MDN developer website provides a great place to learn more about web development.
Let’s Get Started!
Firstly, What Are We Making?
In this tutorial, I’ll teach you how to build a basic, beginner-friendly plugin that will be able to create, duplicate, and modify layers, as well as present the user with a nice user interface. By doing so, my goal is to establish a fundamental knowledge on which you can build on and use it to create your own plugins.
The plugin we’ll be building is called Mosaic, and is effectively a “pattern generator”. Feed it your layers, tweak a few settings, and it’ll create a pattern:
A bit of history: Mosaic is inspired in large part by an old-school Adobe Fireworks plugin called Twist-and-Fade. Twist-and-Fade was pretty powerful, able to duplicate a layer any number of times while adjusting its hue, position, rotation, size, and opacity. The plugin was even able to generate animated GIFs, like this one, where it created the frames for the two rotating elements in the cassette tape:
For the purposes of this tutorial, we’ll be building a somewhat similar plugin for Sketch, though intentionally simplified so as to keep the tutorial as accessible as possible. Specifically, our plugin will be able to:
Duplicate any Sketch layer (bitmap or vector) and tweak the duplicates’ layer’s position, rotation, and opacity. This will give us an introduction to manipulating layers using Sketch’s JavaScript APIs.
Display a user interface created using HTML, CSS, and JS, which will teach you about how to easily create an interface for the plugin, by using web technologies that you may already be familiar with. The plugin interface is pretty important since it’s how we’ll gather the user’s inputs regarding how the user wants the resulting mosaic image to look.
Creating Our Base Plugin In Ten Seconds Flat
First, we’ll be creating the “base” (or template) for the plugin we want to build. We could create all the necessary files and folders that make up a plugin manually, but luckily we don’t have to — because Sketch can do it for us. After we’ve generated the template plugin, we’ll be able to customize it as we see fit.
There’s a really quick and easy technique we can use to create the template plugin, which is pretty much my go-to method when I need to whip a plugin together to solve whatever problem I’m dealing with at a given moment. Here’s how it works:
With Sketch open, check the menu bar at the top of the screen and click Plugins -> Run Script. This will open up a dialog box that we can use to test and run the code. We can also save any code we enter in it as a plugin, which is the part we’re specifically interested in right now.
Clear whatever code is already in this dialog and replace it with the following demo code:
const UI = require("sketch/ui");
UI.message("😍 Hey there, you fantastic plugin developer you! This is your plugin! Talking to you from the digital computer screen! In Sketch! Simply stupendous!");
Next, hit Save Script as Plugin in the bottom-left of the window, enter whatever name you’d like for this plugin to have (in our case, this is “Mosaic”), then Save Script as Plugin once more.
Believe it or not, we’re already done — all that’s left is to eat the cake we just baked. Here comes the fun part. Opening the Plugins menu once again, you should see something like this: your brand-spanking-new plugin listed as “Mosaic”! Click on it!
Congratulations, you’ve just written your first Sketch plugin!
What you should see after clicking “Mosaic” should be like the short video above, with an unobtrusive tooltip message appearing at the bottom of the screen beginning with the words “Hey there…” — which is exactly what the code we pasted in tells it to do. This is what it makes this technique so great: it makes it easy to paste, modify and test code without having to build a plugin from scratch. If you’re familiar with or have ever played with your browser’s web console, this is basically that. Having this tool in your back pocket as you build and test code is a must-have.
Let’s do a quick rundown of what the code you added does:
First, it imports the sketch/uimodule of Sketch’s built-in JS library, and assigns it to the UI variable. This module contains a couple of useful interface-related methods, one of which we’ll use:
const UI = require("sketch/ui");
Next, it calls the message method (which is part of the sketch/ui module) with the string of text we want displayed in the tooltip we saw:
UI.message("😍 Hey there, you fantastic plugin developer you! This is your plugin! Talking to you from the digital computer screen! In Sketch! Simply stupendous!");
The message() method provides a great way to present an unobtrusive message to the user; it’s great for cases where you don’t need to steal focus (non-modal) and don’t need any fancy buttons or text fields. There’s also other ways to present common UI elements like alerts, prompts, and such, some of which we’ll be using as we build Mosaic.
Customizing Our Plugin’s Metadata
We now have a basic plugin to start from, but we still need to tweak it further and make it truly ours. Our next step will be to change the plugin’s metadata.
For this step, we’ll need to peek into what’s called the plugin bundle. When you hit save in the ‘Run Script’ window, Sketch saved your plugin as a folder named Mosaic.sketchplugin that you can find in the ~/Library/Application Support/com.bohemiancoding.sketch3/Plugins directory. That’s a bit long and annoying to remember; as a shortcut, you can also pull it up via Plugins -> Manage Plugins -> (right-click your plugin) -> Reveal Plugins Folder. Even though it appears in Finder as a single file, it’s actually a folder containing everything our plugin needs for Sketch to run it. The reason it appears as a single file despite being a folder is because when you first installed Sketch, Sketch registered the .sketchplugin extension as a “bundle” (a special kind of folder that appears as a file) and asked for it to automatically open in Sketch when opened.
Let’s take a peek inside. Right-click Mosaic.sketchplugin, then click “Show Package Contents”. Inside, you should see the following directory structure:
You might be wondering why there’s a file in there with the extension .cocoascript. Don’t worry — it’s just a regular JavaScript file, and only contains the code we entered earlier. Go ahead and rename this file to index.js, which will change the directory structure to look like the one below:
The most common way of organizing the files inside a plugin bundle is as follows: your code (JavaScript) and manifest.json belong in Sketch/, and resources (think images, audio files, text files, etc.) belong in Resources/.
Let’s start by tweaking the file named manifest.json. Open it inside your favorite code editor, such as Visual Studio Code or Atom.
You’ll see that at the moment there’s relatively little inside here, but we’ll add more soon. The plugin manifest serves primarily two purposes:
First, it provides metadata that describes the plugin to the user — things like its name, version, the author’s name, and so on. Sketch uses this information in the Sketch -> Preferences -> Plugins dialog to create a listing and description for your plugin.
Second, it also tells Sketch about how to get down to your business; that is, it tells Sketch how you’d like your plugin’s menu to look, what hotkeys to assign to your plugin, and where your plugin’s code lives (so Sketch can run it).
Considering purpose #1, describing the plugin to the user, you’ll probably notice that right now there’s no description or author given, which would be confusing for the user and make the plugin difficult to identify. Let’s fix that by adjusting the relevant keys’ values to:
{
"description": "Generate awesome designs and repeating patterns from your layers!",
"author": "=> Your name here
Next, let’s adjust the plugin’s identifier. This identifier uses what is called a “reverse domain notation” which is a really concise (or boring, take your pick) way to say “take your site’s domain, reverse the order, then put your product’s name at the end.” This will come out something like: com.your-company-or-your-name-its-not-that-big-a-deal.yourproduct.
You don’t have to stick to this naming convention — you can put whatever you want here, so long as it’s unique enough to avoid conflicts with other plugins (though it’s probably a good idea to stick to the RDN format, especially as it provides a simple, reusable system for your plugin identifiers).
To that effect, change your identifier to com.your-name.mosaic:
{
"identifier": "com.your-name.mosaic"
}
I personally like to take all metadata related keys (title, author, identifier, etc.) and group them near the top of the manifest so they’re not spread out all over the place and help preserve my sanity when I need to find them.
Next, let’s take a look at the menu and commands keys. These two are responsible for telling Sketch what code to call and in response to what.
If you look at the menu key, you’ll see it contains a title key, whose value is the name our plugin will show up with in the Plugins menu. It also has an items key, which is a list of command identifiers:
Right now there’s only one command identifier in this list, "com.bohemiancoding.sketch.runscriptidentifier". Command identifiers always point to a command in the commands list. Right now our plugin only has one command, which is the one with this identifier:
Whenever you add a command identifier to a menu entry, Sketch will look up the command entry that has that identifier and will display the value of its name key (which in this case is “Mosaic”) and will show it in your plugin’s menu instead of the identifier.
As for the role commands play, we can think of a command entry as a way to tell Sketch what function in our plugin’s JavaScript code we want to run when that command is invoked, the “invocation” usually being the user’s click on the associated menu item. The command entry doesn’t do anything on its own, it’s just JSON — it simply provides a description to Sketch of where to look for the JavaScript it needs to run when the command is invoked.
So far, we’ve talked about what a command’s name and identifier keys do, but there are two other keys in a command that need to be addressed: script and handlers.
The script key tells Sketch where the JavaScript file that it should run is. Note how Sketch assumes that the script file in question is in the Sketch/ folder, which is why for simplicity’s sake you’ll want to make sure all your JavaScript code lives somewhere under the Sketch/ folder. Before we move on from this key it’s important that you make sure you change this key’s value to index.js, just like we renamed the file earlier. Otherwise, Sketch won’t be able to find and run your JavaScript file.
The value of the handlers key is what Sketch looks at to determine what function in your JavaScript to call. Here, we only have one handler set: run, with the value onRun. run is the name of a predefined, built-in Sketch action. This run action will always be called when a user clicks a menu item that references this command. onRun is the name of a function in the auto-generated script.cocoascript file (which we renamed to index.js), and the function we want to be called when the run event occurs, i.e., when the user clicks the menu item.
In the example we have so far, this process plays out something like this:
The user clicks our menu item.
Sketch finds the command associated with that menu item.
Sketch finds the script file the command refers to and runs it (which in this case means it executes the JavaScript in index.js).
Since this command was invoked by a menu item click, it’s considered a run action. That means Sketch will look at the command’s handlers.run value for the function to call next, which in this case is onRun.
Sketch calls the onRun function.
Commands are most commonly called in response to a user clicking on one of your menu items, but they can also be called in response to other user actions, such as the user changing the selection or a property on a layer. However, for this plugin, we won’t be using any of these other actions. (You can learn more about actions and how they work in the Action API help page.)
Before we move on from this manifest, we’ll want to make two other tweaks. Right now, our menu has the structure:
Mosaic
└ Mosaic
…which is a bit redundant since our plugin only has one menu item. It also adds a bit of unnecessary friction for our user as our plugin now takes two clicks to invoke rather than one. We can fix this by adding isRoot: true to our menu:
This tells Sketch to place the first level of menu items directly under the Plugins menu, rather than nesting them under the menu’s title.
Hit save and return to Sketch. You should see that now Mosaic -> Mosaic has been replaced by just Mosaic — perfect!
As for our second tweak, let’s go ahead and rename this command identifier to something less unwieldy. Since command identifiers only need to be unique within the context of an individual plugin, we can safely rename it to something more concise and obvious, like "open":
Before we move on, it’s useful to note that menus can contain also other menus. You could easily create a sub-menu by nesting another { title: ..., items: ... } entry inside another menu’s items list:
So far, we’ve written some demo code and customized our plugin’s manifest. We’ll now move on to creating its user interface, which is essentially a web page embedded in a window (similarly to the browsers you’re familiar with using):
The Window
Mosaic’s user interface design has its own window, which we can consider the most basic component; we’ll start with it. In order to create and display a window, we’ll have to make use of a class that’s built into macOS by default, called NSWindow. Over the remainder of this tutorial, we’ll actually be doing this quite a bit (using built-in APIs like NSWindow) which might seem a little daunting if you’re unfamiliar with it, but don’t worry — I’ll explain everything along the way!
Note: While we’re talking about built-in APIs, the reason we’re able to use this class at all is thanks to a bridge present in the JavaScript runtime used by Sketch plugins. This bridge automatically imports these built-in classes, methods, and functions that would normally only be available to native applications.
Open Sketch/index.js in your code editor, delete what’s already there, and paste in the following:
Let’s take a look at what this first bit of code does:
function onRun(context){
Remember earlier when we talked about commands and how they function, and we told Sketch to call in response to a menu-click was called onRun? (If you need a refresher, revisit that part above, then come back.) All this bit does is create that function. You’ll also notice our onRun function takes a context argument. This is an argument Sketch will call your command handlers with that can provide us with certain information. Later on, we’ll use it in order to get the URL of our plugin bundle on the user’s computer.
First, we call alloc() on NSWindow; this basically means “set aside some memory for an instance of NSWindow”. It’s sufficient to know that you’ll have to do this for every instance of a native class you want to create. The alloc method is available in every native class.
Next, we call NSWindow‘s initializer method (that is, the method that actually creates an instance of NSWindow), which is named initWithContentRect:styleMask:backing:defer:. You’ll notice that’s different from what we call in our code above — it’s got a bunch of colons (:) between every argument. Since we can’t use that syntax in JavaScript, Sketch conveniently renames it to something we can actually use by replacing the colons with underscores, which is how we get its JS name: initWithContentRect_styleMask_backing_defer.
Next, we pass in each of the arguments the method needs. For the first argument, contentRect, we supply a rectangle with a size large enough for our user interface.
For styleMask, we use a bitmask which says that we want our window to have a close button, a title bar, and to be resizable.
The next two arguments, backing and defer, are always going to be set to NSBackingStoreBuffered and false, so we don’t really need to worry about them. (The documentation for this method goes into further detail as to why this is.)
Here we set NSWindow‘s releasedWhenClosed property to false, which means: “Hey! don’t delete this window from memory just because the user closes it.” Then we call makeKeyAndOrderFront(null) which means: “Move this window to the forefront, and give it keyboard focus.”
Web View: The Interface
To make things easier, I’ve already written the HTML and CSS code of the plugin’s web user interface we’re going to be using; the only remaining code we’re going to have to add to it will deal with making sure we’re able to communicate between it and our Sketch plugin code.
Next, download the HTML and CSS code. Once you’ve downloaded it, extract it, then move the folder named “web-ui” into our plugin’s Resources folder.
Note: Writing and optimizing the actual HTML/CSS code is outside of the scope of this tutorial, as its focus is on JavaScript which powers the plugin’s core features; but there are a ton of tutorials on the web on this topic, should you want to learn more.
If you run our plugin now, you’ll see that it shows a window — yay, progress! But it’s empty, without a title, and not particularly useful yet. We need to get it to show our web interface. In order to do that, we’ll need to use another native class, WKWebView, which is a view specifically made for displaying web content.
We’ll add the code needed to create our WKWebView beneath the code we wrote for our window:
function onRun(context){
// Create window
const window = NSWindow.alloc().initWithContentRect_styleMask_backing_defer(
NSMakeRect(0, 0, 145, 500),
NSWindowStyleMaskClosable | NSWindowStyleMaskTitled | NSWindowStyleMaskResizable,
NSBackingStoreBuffered,
false
);
window.releasedWhenClosed = false;
// Create web view, and set it as the view for our window to display
const webView = WKWebView.alloc().init();
window.contentView = webView;
// Load our UI into the web view
const webUIFolderURL = context.scriptURL
.URLByDeletingLastPathComponent()
.URLByAppendingPathComponent("../Resources/web-ui/");
const indexURL = webUIFolderURL.URLByAppendingPathComponent("index.html");
webView.loadFileURL_allowingReadAccessToURL(indexURL, webUIFolderURL);
// Make window key and move to front
window.makeKeyAndOrderFront(nil);
};
If we run our plugin now, we’ll see that now we’ve got a window open that displays our web user interface. Success!
Again, before moving on, let’s examine what the code we added does:
const webView = WKWebView.alloc().init();
This should look familiar — it’s basically the same as what we did when we made our NSWindow: allocate memory for a web view, then initialize it.
window.contentView = webView;
This line of code tells our window to display the web view we just made.
Here our goal is to create a URL that points to the web-ui folder that we made earlier. In order to get that URL, we need some way to figure out where our plugin’s bundle is in the user’s filesystem. Here we use the context.scriptURL property, which gives us the URL of the currently running script. However, this doesn’t give us a JavaScript String as you might expect, but an instance of a native class, NSURL, that has a few methods on it that make manipulating URL strings easier.
Calling URLByDeletingLastPathComponent() again gives us file://path-to-your-plugin/Contents/
And lastly, adding Resources/web-ui/ onto the end using URLByAppendingPathComponent("Resources/web-ui/") gives us file://path-to-your-plugin/Contents/Resources/web-ui/
We also need to create a second URL that points directly to the index.html file:
Alright. So far, we have a window that displays our web user interface, just like we wanted. However, it’s not quite yet complete — our original design doesn’t have a title bar (or “chrome”), but our current window does. There’s also the fact that when we click inside a Sketch document, that document moves in front of our window, which isn’t what we want — we want the user to be able to interact with the plugin window and the Sketch document without having to constantly refocus from one window to the other.
To fix this, we first need to get rid of the default window chrome and keep only the buttons. Adding the two lines of code below will get rid of the title bar.
Note:Like before, all of the properties and methods we’re using below are documented in NSWindow‘s documentation page.
These next two lines of code will remove the window buttons (also known as “traffic lights” in MacOS lingo) that we don’t need — “zoom” and “minimize” — leaving only the “close” button:
Next, we need to do something to keep our floating plugin window on top of other windows, so the user can interact with their Sketch documents without having to worry about the Mosaic’s window disappearing. We can use a special type of NSWindow for this, called NSPanel, which is able to “stay on top” of other windows. All that’s needed for this is to change NSWindow to NSPanel, which is a single-line code change:
We can also tweak our window so that it automatically reopens in the last position it was at:
window.frameAutosaveName = "mosaic-panel-frame";
This line basically says “remember this window’s position by saving it with Sketch’s preferences under the key mosaic-panel-frame”.
All together, we now have the following code:
function onRun(context){
// Create window
const window = NSPanel.alloc().initWithContentRect_styleMask_backing_defer(
NSMakeRect(0, 0, 145, 500),
NSWindowStyleMaskClosable | NSWindowStyleMaskTitled | NSWindowStyleMaskResizable,
NSBackingStoreBuffered,
false
);
window.becomesKeyOnlyIfNeeded = true;
window.floatingPanel = true;
window.frameAutosaveName = "mosaic-panel-frame";
window.releasedWhenClosed = false;
window.standardWindowButton(NSWindowZoomButton).hidden = true;
window.standardWindowButton(NSWindowMiniaturizeButton).hidden = true;
window.titlebarAppearsTransparent = true;
window.titleVisibility = NSWindowTitleHidden;
window.backgroundColor = NSColor.colorWithRed_green_blue_alpha(1, 0.98, 0.98, 1);
// Create web view, and set it as the view for our window to display
const webView = WKWebView.alloc().init();
window.contentView = webView;
// Load our UI into the webview
const webUIFolderURL = context.scriptURL
.URLByDeletingLastPathComponent()
.URLByAppendingPathComponent("../Resources/web-ui/");
const indexURL = webUIFolderURL.URLByAppendingPathComponent("index.html");
webView.loadFileURL_allowingReadAccessToURL(indexURL, webUIFolderURL);
// Make window key and move to front
window.makeKeyAndOrderFront(nil);
};
Organizing The Code
Before we move to the next part, it’s a good idea to organize our code so that it’s easier to navigate and tweak. Since we still have a lot more code to add and we want to avoid index.js becoming a messy dumping ground for all of our code, let’s split things up a bit and move our UI-specific code into a file named ui.js, under the Sketch folder. We’ll also extract some of the UI tasks we do, like creating the web view and window, into their own functions.
Create a new file called ui.js and insert the code below inside it:
There are a couple of key changes we made here that are important to note. Besides the fact that we’ve created specific functions for the creation, hiding and showing of our window and its web view, we’ve also modularized our user interface code.
Notice the module.exports = { loadAndShow, cleanup } line at the bottom? This is a way for us to specify exactly what objects and functions scripts who import this UI code can use (and hiding those we don’t want to them to worry about), which means we now have a more organized API for interacting with, showing and destroying our UI.
Let’s see what this looks like in practice. Back in index.js, remove the old code and add the following:
const UI = require("./ui");
function onRun(context){
UI.loadAndShow(context.scriptURL);
};
We’re using a special function that Sketch automatically makes available to us, require, to import our ui.js code and assign the returned module to the UI variable. This gives us access to a simplified API for triggering our user interface. Things are much tidier now and easy to find!
Conclusion
Well done — you’ve come far! In the next part of this tutorial, we’ll give our web UI the ability to send us a message when the “Apply” button is clicked, and we’ll focus on the main plugin functionality: actually generating layer mosaics!