Εξερεύνηση του CSS Paint API: Image Fragmentation Effect

Κόμβος πηγής: 1016584

In το προηγούμενο άρθρο μου, δημιούργησα ένα εφέ κατακερματισμού χρησιμοποιώντας μάσκα CSS και προσαρμοσμένες ιδιότητες. Ήταν ένα προσεγμένο αποτέλεσμα, αλλά έχει ένα μειονέκτημα: χρησιμοποιεί πολύ κώδικα CSS (που δημιουργήθηκε με χρήση Sass). Αυτή τη φορά θα επαναλάβω το ίδιο εφέ, αλλά θα βασιστώ στο νέο Paint API. Αυτό μειώνει δραστικά την ποσότητα του CSS και αφαιρεί εντελώς την ανάγκη για Sass.

Να τι φτιάχνουμε. Όπως και στο προηγούμενο άρθρο, μόνο το Chrome και το Edge το υποστηρίζουν προς το παρόν.

Δες αυτό? Όχι περισσότερες από πέντε δηλώσεις CSS και παρόλα αυτά έχουμε ένα πολύ ωραίο animation αιώρησης.

Τι είναι το Paint API;

Η Paint API αποτελεί μέρος του έργου Χουντίνι. Ναι, «Χουντίνι» ο περίεργος όρος για τον οποίο μιλούν όλοι. Πολλά άρθρα καλύπτω ήδη τη θεωρητική πτυχή του, οπότε δεν θα σας ενοχλήσω με περισσότερα. Αν πρέπει να το συνοψίσω με λίγα λόγια, θα έλεγα απλά: είναι το μέλλον του CSS. Το Paint API (και τα άλλα API που εμπίπτουν στην ομπρέλα του Houdini) μας επιτρέπουν να επεκτείνουμε το CSS με τις δικές μας λειτουργίες. Δεν χρειάζεται πλέον να περιμένουμε την κυκλοφορία νέων χαρακτηριστικών γιατί μπορούμε να το κάνουμε μόνοι μας!

Από η προδιαγραφή:

Ένα API που επιτρέπει στους προγραμματιστές ιστού να ορίσουν ένα προσαρμοσμένο CSS <image> με javascript [sic], που θα ανταποκρίνεται στις αλλαγές στυλ και μεγέθους.

Και από ο εξηγητής:

Το CSS Paint API αναπτύσσεται για τη βελτίωση της επεκτασιμότητας του CSS. Συγκεκριμένα, αυτό επιτρέπει στους προγραμματιστές να γράψουν μια συνάρτηση ζωγραφικής που μας επιτρέπει να σχεδιάζουμε απευθείας σε ένα φόντο, περίγραμμα ή περιεχόμενο στοιχείων [sic].

Νομίζω ότι η ιδέα είναι αρκετά ξεκάθαρη. Μπορούμε να ζωγραφίσουμε ότι θέλουμε. Ας ξεκινήσουμε με μια πολύ βασική επίδειξη χρωματισμού φόντου:

  1. Προσθέτουμε το εργαστήριο βαφής χρησιμοποιώντας CSS.paintWorklet.addModule('your_js_file').
  2. Καταχωρούμε μια νέα μέθοδο βαφής που ονομάζεται draw.
  3. Μέσα σε αυτό, δημιουργούμε ένα paint() λειτουργία όπου κάνουμε όλη τη δουλειά. Και μάντεψε τι? Όλα είναι σαν να δουλεύεις <canvas>. Οτι ctx είναι το δισδιάστατο πλαίσιο, και απλώς χρησιμοποίησα μερικές γνωστές συναρτήσεις για να σχεδιάσω ένα κόκκινο ορθογώνιο που καλύπτει ολόκληρη την περιοχή.

Αυτό μπορεί να φαίνεται αδιανόητο με την πρώτη ματιά, αλλά παρατηρήστε ότι η κύρια δομή είναι πάντα η ίδια: τα τρία παραπάνω βήματα είναι το μέρος "αντιγραφή/επικόλληση" που επαναλαμβάνετε για κάθε έργο. Το πραγματικό έργο είναι ο κώδικας που γράφουμε μέσα στο paint() λειτουργία.

Ας προσθέσουμε μια μεταβλητή:

Όπως μπορείτε να δείτε, η λογική είναι αρκετά απλή. Ορίζουμε τον λήπτη inputProperties με τις μεταβλητές μας ως πίνακα. Προσθέτουμε properties ως τρίτη παράμετρος για να paint() και αργότερα παίρνουμε τη μεταβλητή μας χρησιμοποιώντας properties.get().

Αυτό είναι! Τώρα έχουμε όλα όσα χρειαζόμαστε για να δημιουργήσουμε το περίπλοκο εφέ κατακερματισμού μας.

Κατασκευή της μάσκας

Ίσως αναρωτιέστε γιατί το paint API δημιουργεί ένα εφέ κατακερματισμού. Είπαμε ότι είναι ένα εργαλείο για τη σχεδίαση εικόνων, οπότε πώς θα μας επιτρέψει να κατακερματίσουμε μια εικόνα;

Στο προηγούμενο άρθρο, έκανα το εφέ χρησιμοποιώντας διαφορετικό στρώμα μάσκας, όπου το καθένα είναι ένα τετράγωνο που ορίζεται με μια διαβάθμιση (θυμηθείτε ότι η διαβάθμιση είναι μια εικόνα), οπότε πήραμε ένα είδος μήτρας και το κόλπο ήταν να προσαρμόσουμε το κανάλι άλφα του καθενός ένα ξεχωριστά.

Αυτή τη φορά, αντί να χρησιμοποιούμε πολλές διαβαθμίσεις, θα ορίσουμε μόνο μία προσαρμοσμένη εικόνα για τη μάσκα μας και αυτή η προσαρμοσμένη εικόνα θα αντιμετωπίζεται από το paint API μας.

Ένα παράδειγμα παρακαλώ!

Στα παραπάνω, έχω δημιουργήσει μια εικόνα με αδιαφανές χρώμα που καλύπτει το αριστερό μέρος και ένα ημιδιάφανο που καλύπτει το δεξί μέρος. Η εφαρμογή αυτής της εικόνας ως μάσκα μας δίνει το λογικό αποτέλεσμα μιας ημιδιαφανούς εικόνας.

Τώρα το μόνο που χρειάζεται να κάνουμε είναι να χωρίσουμε την εικόνα μας σε περισσότερα μέρη. Ας ορίσουμε δύο μεταβλητές και ας ενημερώσουμε τον κώδικά μας:

Το σχετικό τμήμα του κώδικα είναι το εξής:

const n = properties.get('--f-n');
const m = properties.get('--f-m'); const w = size.width/n;
const h = size.height/m; for(var i=0;i<n;i++) { for(var j=0;j<m;j++) { ctx.fillStyle = 'rgba(0,0,0,'+(Math.random())+')'; ctx.fillRect(i*w, j*h, w, h);
}
}

N και M ορίζουμε τη διάσταση του πίνακα των ορθογωνίων μας. W και H είναι το μέγεθος κάθε ορθογωνίου. Τότε έχουμε ένα βασικό FOR βρόχο για να γεμίσετε κάθε ορθογώνιο με ένα τυχαίο διαφανές χρώμα.

Με λίγη JavaScript, λαμβάνουμε μια προσαρμοσμένη μάσκα που μπορούμε εύκολα να ελέγξουμε προσαρμόζοντας τις μεταβλητές CSS:

Τώρα, πρέπει να ελέγξουμε το κανάλι άλφα για να δημιουργήσουμε το εφέ εξασθένισης κάθε ορθογωνίου και να δημιουργήσουμε το εφέ κατακερματισμού.

Ας εισαγάγουμε μια τρίτη μεταβλητή που χρησιμοποιούμε για το κανάλι άλφα που επίσης αλλάζουμε κατά την τοποθέτηση του δείκτη.

Ορίσαμε μια προσαρμοσμένη ιδιότητα CSS ως α <number> ότι κάνουμε μετάβαση από το 1 στο 0, και η ίδια ιδιότητα χρησιμοποιείται για τον ορισμό του καναλιού άλφα των ορθογωνίων μας. Τίποτα φανταχτερό δεν θα συμβεί κατά την αιώρηση, επειδή όλα τα ορθογώνια θα ξεθωριάσουν με τον ίδιο τρόπο.

Χρειαζόμαστε ένα κόλπο για να αποτρέψουμε το ξεθώριασμα όλων των ορθογωνίων ταυτόχρονα, αντί να δημιουργείται καθυστέρηση μεταξύ τους. Ακολουθεί μια απεικόνιση για να εξηγήσω την ιδέα που πρόκειται να χρησιμοποιήσω:

Το παραπάνω δείχνει την κινούμενη εικόνα άλφα για δύο ορθογώνια. Πρώτα ορίζουμε μια μεταβλητή L που θα πρέπει να είναι μεγαλύτερη ή ίση με 1 και στη συνέχεια για κάθε ορθογώνιο του πίνακα μας (δηλαδή για κάθε κανάλι άλφα) εκτελούμε μια μετάβαση μεταξύ X και Y όπου X - Y = L οπότε έχουμε την ίδια συνολική διάρκεια για όλα τα κανάλια άλφα. Το X πρέπει να είναι μεγαλύτερο ή ίσο με 1 και το Y μικρότερο ή ίσο με 0.

Περιμένετε, η τιμή άλφα δεν πρέπει να βρίσκεται στο εύρος [1 0], σωστά ?

Ναι, θα έπρεπε! Και όλα τα κόλπα στα οποία εργαζόμαστε βασίζονται σε αυτό. Παραπάνω, το άλφα κινείται από το 8 στο -2, που σημαίνει ότι έχουμε ένα αδιαφανές χρώμα στο [8 1] σειρά, ένα διαφανές στο [0 -2] εύρος και μια κινούμενη εικόνα εντός [1 0]. Με άλλα λόγια, οποιαδήποτε τιμή μεγαλύτερη από 1 θα έχει το ίδιο αποτέλεσμα με το 1 και οποιαδήποτε τιμή μικρότερη από 0 θα έχει το ίδιο αποτέλεσμα με 0.

Κινούμενα σχέδια μέσα [1 0] δεν θα συμβεί ταυτόχρονα και για τα δύο παραλληλόγραμμά μας. Το ορθογώνιο 2 θα φτάσει [1 0] πριν από το Ορθογώνιο 1 θα. Το εφαρμόζουμε σε όλα τα κανάλια άλφα για να λάβουμε τις καθυστερημένες κινούμενες εικόνες.

Στον κώδικα μας θα ενημερώσουμε αυτό:

rgba(0,0,0,'+(o)+')

…σ 'αυτό:

rgba(0,0,0,'+((Math.random()*(l-1) + 1) - (1-o)*l)+')

L είναι η μεταβλητή που απεικονίστηκε προηγουμένως, και O είναι η τιμή της μεταβλητής CSS που μεταβαίνει από το 1 στο 0

Όταν O=1, έχουμε (Math.random()*(l-1) + 1). Λαμβάνοντας υπόψη το γεγονός ότι η random() η συνάρτηση μας δίνει μια τιμή εντός του [0 1] εύρος, η τελική τιμή θα είναι στο [L 1]εύρος.

Όταν O=0, έχουμε (Math.random()*(l-1) + 1 - l) και μια τιμή με το [0 1-L] εύρος.

L είναι η μεταβλητή μας για τον έλεγχο της καθυστέρησης.

Ας το δούμε στην πράξη:

Πλησιάζουμε. Έχουμε ένα δροσερό εφέ κατακερματισμού αλλά όχι αυτό που είδαμε στην αρχή του άρθρου. Αυτό δεν είναι τόσο ομαλό.

Το θέμα σχετίζεται με random() λειτουργία. Είπαμε ότι κάθε άλφα κανάλι πρέπει να κινείται μεταξύ X και Y, οπότε λογικά αυτές οι τιμές πρέπει να παραμείνουν οι ίδιες. Αλλά το paint() η συνάρτηση ονομάζεται δέσμη κατά τη μετάβαση, οπότε κάθε φορά, το random() λειτουργία μας δίνει διαφορετικά X και Y Τιμές για κάθε κανάλι άλφα. εξ ου και το «τυχαίο» αποτέλεσμα που έχουμε.

Για να διορθωθεί αυτό, πρέπει να βρούμε έναν τρόπο αποθήκευσης της παραγόμενης τιμής ώστε να είναι πάντα η ίδια για κάθε κλήση του paint() λειτουργία. Ας εξετάσουμε μια ψευδοτυχαία συνάρτηση, μια συνάρτηση που δημιουργεί πάντα την ίδια ακολουθία τιμών. Με άλλα λόγια, θέλουμε να ελέγξουμε τον σπόρο.

Δυστυχώς, δεν μπορούμε να το κάνουμε αυτό με το ενσωματωμένο JavaScript random() λειτουργία, έτσι όπως κάθε καλός προγραμματιστής, ας επιλέξουμε έναν από το Stack Overflow:

const mask = 0xffffffff;
const seed = 30; /* update this to change the generated sequence */
let m_w = (123456789 + seed) & mask;
let m_z = (987654321 - seed) & mask; let random = function() { m_z = (36969 * (m_z & 65535) + (m_z >>> 16)) & mask; m_w = (18000 * (m_w & 65535) + (m_w >>> 16)) & mask; var result = ((m_z << 16) + (m_w & 65535)) >>> 0; result /= 4294967296; return result;
}

Και το αποτέλεσμα είναι:

Έχουμε το εφέ κατακερματισμού μας χωρίς πολύπλοκο κώδικα:

  • ένα βασικό ένθετο βρόχο για τη δημιουργία ορθογωνίων NxM
  • ένας έξυπνος τύπος για το κανάλι άλφα για τη δημιουργία της καθυστέρησης μετάβασης
  • ένα έτοιμο random() συνάρτηση που λαμβάνεται από το Δίκτυο

Αυτό είναι! Το μόνο που έχετε να κάνετε είναι να εφαρμόσετε το mask ιδιοκτησία σε οποιοδήποτε στοιχείο και προσαρμόστε τις μεταβλητές CSS.

Καταπολεμώντας τα κενά!

Αν παίξετε με τα παραπάνω demos θα παρατηρήσετε, σε κάποια συγκεκριμένη περίπτωση, περίεργα κενά ανάμεσα στα ορθογώνια

Για να το αποφύγουμε αυτό, μπορούμε να επεκτείνουμε το εμβαδόν κάθε ορθογωνίου με μια μικρή μετατόπιση.

Ενημερώνουμε αυτό:

ctx.fillRect(i*w, j*h, w, h);

…με αυτό:

ctx.fillRect(i*w-.5, j*h-.5, w+.5, h+.5);

Δημιουργεί μια μικρή επικάλυψη μεταξύ των ορθογωνίων που αντισταθμίζει τα κενά μεταξύ τους. Δεν υπάρχει ιδιαίτερη λογική με την αξία 0.5 Χρησιμοποίησα. Μπορείτε να κάνετε μεγαλύτερο ή μικρότερο ανάλογα με την περίπτωση χρήσης σας.

Θέλετε περισσότερα σχήματα;

Μπορεί τα παραπάνω να επεκταθούν ώστε να θεωρηθεί κάτι περισσότερο από το ορθογώνιο σχήμα; Σίγουρα μπορεί! Ας μην ξεχνάμε ότι μπορούμε να χρησιμοποιήσουμε τον Καμβά για να σχεδιάσουμε οποιοδήποτε είδος σχήματος — σε αντίθεση με καθαρά σχήματα CSS όπου μερικές φορές χρειαζόμαστε κάποιον τυχαίο κώδικα. Ας προσπαθήσουμε να δημιουργήσουμε αυτό το τριγωνικό εφέ κατακερματισμού.

Μετά από αναζήτηση στο διαδίκτυο, βρήκα κάτι που λέγεται Τριγωνισμός Delaunay. Δεν θα μπω στη βαθιά θεωρία πίσω από αυτό, αλλά είναι ένας αλγόριθμος για ένα σύνολο σημείων για τη σχεδίαση συνδεδεμένων τριγώνων με συγκεκριμένες ιδιότητες. Υπάρχουν πολλές έτοιμες προς χρήση υλοποιήσεις του, αλλά θα πάμε με τον Delaunator γιατί υποτίθεται ότι είναι το πιο γρήγορο από το μάτσο.

Αρχικά ορίζουμε ένα σύνολο σημείων (θα χρησιμοποιήσουμε random() εδώ) στη συνέχεια εκτελέστε το Delauntor για να δημιουργήσετε τα τρίγωνα για εμάς. Σε αυτήν την περίπτωση, χρειαζόμαστε μόνο μία μεταβλητή που καθορίζει τον αριθμό των σημείων.

const n = properties.get('--f-n');
const o = properties.get('--f-o');
const w = size.width;
const h = size.height;
const l = 7; var dots = [[0,0],[0,w],[h,0],[w,h]]; /* we always include the corners */
/* we generate N random points within the area of the element */
for (var i = 0; i < n; i++) { dots.push([random() * w, random() * h]);
}
/**/
/* We call Delaunator to generate the triangles*/
var delaunay = Delaunator.from(dots);
var triangles = delaunay.triangles;
/**/
for (var i = 0; i < triangles.length; i += 3) { /* we loop the triangles points */ /* we draw the path of the triangles */ ctx.beginPath(); ctx.moveTo(dots[triangles[i]][0] , dots[triangles[i]][1]); ctx.lineTo(dots[triangles[i + 1]][0], dots[triangles[i + 1]][1]); ctx.lineTo(dots[triangles[i + 2]][0], dots[triangles[i + 2]][1]); ctx.closePath(); /**/ var alpha = (random()*(l-1) + 1) - (1-o)*l; /* the alpha value */ /* we fill the area of triangle with the semi-transparent color */ ctx.fillStyle = 'rgba(0,0,0,'+alpha+')'; /* we consider stroke to fight the gaps */ ctx.strokeStyle = 'rgba(0,0,0,'+alpha+')'; ctx.stroke(); ctx.fill();
} 

Δεν έχω τίποτα άλλο να προσθέσω στα σχόλια στον παραπάνω κώδικα. Απλώς χρησιμοποίησα μερικά βασικά πράγματα JavaScript και Canvas και όμως έχουμε ένα πολύ ωραίο αποτέλεσμα.

Μπορούμε να φτιάξουμε ακόμα περισσότερα σχήματα! Το μόνο που έχουμε να κάνουμε είναι να βρούμε έναν αλγόριθμο για αυτό.

Δεν μπορώ να προχωρήσω χωρίς να κάνω το εξάγωνο!

Πήρα τον κωδικό από αυτό το άρθρο γραμμένο από τον Izan Pérez Cosano. Η μεταβλητή μας είναι τώρα R που θα ορίσει τη διάσταση ενός εξαγώνου.

Ποιο είναι το επόμενο βήμα;

Τώρα που έχουμε δημιουργήσει το εφέ κατακερματισμού μας, ας εστιάσουμε στο CSS. Παρατηρήστε ότι το εφέ είναι τόσο απλό όσο η αλλαγή του opacity τιμή (ή η τιμή οποιασδήποτε ιδιότητας με την οποία εργάζεστε) ενός στοιχείου σε αυτό κατάσταση αιώρησης.

Κινούμενα σχέδια αδιαφάνειας

img { opacity:1; transition:opacity 1s;
} img:hover { opacity:0;
}

Εφέ κατακερματισμού

img { -webkit-mask: paint(fragmentation); --f-o:1; transition:--f-o 1s;
} img:hover { --f-o:0;
}

Αυτό σημαίνει ότι μπορούμε εύκολα να ενσωματώσουμε αυτό το είδος εφέ για να δημιουργήσουμε πιο σύνθετα κινούμενα σχέδια. Εδώ είναι ένα σωρό ιδέες!

Ρυθμιστικό εικόνας με απόκριση

Μια άλλη έκδοση του ίδιου ρυθμιστικού:

Θόρυβος

Φόρτωση οθόνης

Εφέ αιώρησης κάρτας

Αυτό είναι ένα περιτύλιγμα

Και όλα αυτά είναι μόνο η κορυφή του παγόβουνου του τι μπορεί να επιτευχθεί χρησιμοποιώντας το Paint API. Θα τελειώσω με δύο σημαντικά σημεία:

  • Το Paint API είναι 90% <canvas>, άρα όσο περισσότερα γνωρίζετε <canvas>, τόσο πιο φανταχτερά πράγματα μπορείτε να κάνετε. Ο καμβάς χρησιμοποιείται ευρέως, πράγμα που σημαίνει ότι υπάρχει μια δέσμη τεκμηρίωσης και γραφής γι 'αυτό για να σας ενημερώσουν. Γεια σου, εδώ είναι ένα εδώ σε CSS-Tricks!
  • Το Paint API αφαιρεί όλη την πολυπλοκότητα από την πλευρά των πραγμάτων CSS. Δεν υπάρχει καμία σχέση με περίπλοκο και τυχαίο κώδικα για να σχεδιάσετε ωραία πράγματα. Αυτό κάνει τον κώδικα CSS πολύ πιο εύκολο στη συντήρηση, για να μην αναφέρουμε λιγότερο επιρρεπείς σε σφάλματα.

Πηγή: https://css-tricks.com/exploring-the-css-paint-api-image-fragmentation-effect/

Σφραγίδα ώρας:

Περισσότερα από Κόλπα CSS