Parallaxe für vertikales Scrollen | Komentor

Wenn Sie WhatsApp verwenden, haben Sie wahrscheinlich gesehen, dass die Bilder einen kleinen Parallaxeneffekt in der entgegengesetzten Bildlaufrichtung haben. Dies erweckt den Eindruck, dass sich das Bild auf einer tieferen Ebene befindet und der “Bildbehälter” ein Fenster ist: Wenn Sie auf einen Stuhl klettern, ist es, als ob Sie das Fenster heruntergelassen haben (Sie haben nach unten gescrollt), und jetzt können Sie mehr sehen die Landschaft unter dem Fenster (es rollte nach oben) – Hocken hätte den gegenteiligen Effekt.

Einer der größten Nachteile dabei ist, dass Sie Ihr Bild dazu zwingen müssen, größer zu sein als das, was der Benutzer sieht: Wenn Sie ein +/- 10px Bewegung, du musst es schaffen 20px größer und um einen Betrag breiter, der das Verhältnis gleich halten würde. Es gibt andere Möglichkeiten, dies zu tun, zum Beispiel mit Hilfe von UICollectionViewFlowLayoutaber in meinem Fall hatte ich andere Elemente unter dem Bild und wollte nur, dass das Bild diesen Effekt hat, nicht die ganze Zelle.

Der erste Schritt besteht darin, die zu wickeln imageView in einem Container, damit wir es auf und ab bewegen können. Es hätte die gleiche Größe wie das Original imageViewund clipsToBounds einstellen true. Der zweite und dritte Schritt wäre, eine Methode innerhalb der Zelle zu erstellen, die von aufgerufen wird collectionView‘s scrollViewDidScrollund der letzte Schritt besteht darin, die zu aktualisieren imageView‘s Rahmen / Einschränkung.

Wir können dieses Verhalten beschreiben, indem wir es in drei Positionen aufteilen, und gehen der Einfachheit halber davon aus, dass wir einen Vollbildmodus haben collectionView:

  • Wenn der Container vertikal auf dem Bildschirm zentriert ist, sollte dies auch der Fall sein imageView in seinem Behälter sein, mit -10px oben und +10px ganz unten
  • wenn der Behälter den oberen Rand des Bildschirms verlässt, oder seine maxY ist gleich am oberen Rand des Bildschirms, der imageView‘s oben haben sollte 0px oben und -20px ganz unten
  • wenn der Behälter den unteren Rand des Bildschirms verlässt, oder seine x gleich der Höhe des Bildschirms ist, die imageView‘s oben haben sollte 0px oben und -20px ganz unten

Beginnen wir mit der collectionView:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
  collectionView.visibleCells.forEach {
    ($0 as? CustomCell)?.updateImage(in: self.collectionView, in: self.view)
  }
}

Und fahren Sie mit der Logik innerhalb der Zelle fort, wenn wir eine Eigenschaft wie diese hätten:

private lazy var imageView: UIImageView = {
  let iv = UIImageView(image: UIImage(named: "landscape"))
  self.contentView.addSubview(iv)
  
  iv.translatesAutoresizingMaskIntoConstraints = false
  NSLayoutConstraint.activate([
    iv.topAnchor.constraint(equalTo: self.contentView.topAnchor,
    iv.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -60, 
    iv.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor,
    iv.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor)]
  )

  return iv
}()

Wir müssten ein paar Änderungen vornehmen, die wir gleich danach aufschlüsseln werden:

private var imageViewTop: NSLayoutConstraint? = nil 
private lazy var imageView: UIImageView = {
  let container = UIView()
  self.contentView.addSubview(container) 
  
  container.translatesAutoresizingMaskIntoConstraints = false

  NSLayoutConstraint.activate([ 
    container.topAnchor.constraint(equalTo: self.contentView.topAnchor,
    container.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -60,
    container.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
    container.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor)]
  )
  
  let iv = UIImageView(image: UIImage(named: "landscape"))
  container.addSubview(iv) 
  
  iv.translatesAutoresizingMaskIntoConstraints = false
  self.imageViewTop = iv.topAnchor.constraint(equalTo: container.topAnchor)
  
  
  
  NSLayoutConstraint.activate([ 
    imageViewTop!,
    iv.heightAnchor.constraint(equalTo: container.heightAnchor,
                   constant: 20),
    iv.centerXAnchor.constraint(equalTo: container.centerXAnchor)] 
  )
  
  return iv 
}()

Dafür brauchen wir ein neues Grundstück topAnchor Bedingung (1) Da wir dies verwenden werden, um den Parallax-Effekt zu erzeugen, fügen Sie die hinzu imageView zu einem Container (4) und füge das unserem hinzu contentView (2), fügen Sie die vorherigen Einschränkungen zum Container hinzu (3) und richten Sie unsere aus imageView mit dem Behälter (5).

Da wir die erhöhen height durch 20 für die Parallaxe müssten wir auch die erhöhen width so, dass das Verhältnis des Bildes erhalten bleibt; dann müssten wir diesen Wert durch dividieren 2und verwenden Sie es als Konstante für die imageViewDie führenden und nachfolgenden Einschränkungen von . Aber anstatt solche Dinge zu verkomplizieren, verwenden wir tatsächlich centerXAnchor (6), weil die width wird automatisch basierend auf seiner intrinsischen Größe festgelegt.

Wir werden die trotzdem zurücksenden imageView, so dass es einfacher ist, mit ihm zu argumentieren, um beispielsweise sein Bild festzulegen. Wir können jederzeit und sicher auf den Container zugreifen imageView.superview! bei Bedarf, da wir sicher sind, dass es existiert.

Schließlich, wo die wahre Magie passiert, die updateImage Methode:

func updateImage(in collectionView: UICollectionView, in view: UIView) {
  let rect = collectionView.convert(frame, to: view) 
  
  let containerMaxY = rect.maxY - 60
  
  let topInset = collectionView.contentInset.top 
  let bottomInset = collectionView.contentInset.bottom 

  let parallaxRatio = (containerMaxY - topInset) / (view.height + containerHeight - topInset - bottomInset) 
  
  imageTopConstraint?.constant = -20 * min(1, max(0, parallaxRatio)) 
}

Lassen Sie uns das alles aufschlüsseln:

Wir brauchen zuerst das Rect der Zelle in der collectionView‘s mit Ansichtskoordinaten (1).

Unser Szenario ist einfach, mit den Labels’ height bekannt bei 60, aber wir könnten auch ein komplexeres Szenario haben, mit unterschiedlichen Höhen für Hoch- und Querformat; In diesem Fall müssten wir noch ein paar Schritte weitergehen, um es zu finden containerMaxY:

let containerHeight = imageView.superview!.frame.height
let labelsHeight = rect.height - containerHeight
let containerMaxY = rect.maxY - labelsHeight

Die drei Positionen, die wir im Beispiel zu Beginn verwendet haben, waren für einen Vollbildmodus collectionView, aber wir müssen möglicherweise auch ihre Einsätze (2, 3) berücksichtigen, weil wir die Parallaxe in dem Moment beenden möchten, in dem die Zelle nicht mehr sichtbar ist, um den genauesten Effekt zu erzielen. Unsere drei Positionen spiegeln sich in diesen Werten wider:

  • top - containerMaxY == topInset, parallaxRatio == 0 – Das Bild ist am niedrigsten Punkt
  • bottom - containerMaxY == view.height + containerHeight - bottomInset, parallaxRatio == 1 – Das Bild ist am höchsten Punkt
  • center - containerMaxY == (view.height - bottomInset) * 0.5 + containerHeight * 0.5, parallaxRatio == 0.5 – Das Bild wird zentriert

Für die Unterseite der imageView wir berücksichtigen nur den obersten Einschub (4), da dieser der einzige ist, der seinen endgültigen Wert in Bezug auf die übergeordnete Ansicht beeinflusst.

Für den unteren Teil der übergeordneten Ansicht berücksichtigen wir sowohl den oberen Einschub
und der untere Einschub (4), da beide den Endwert beeinflussen (den Punkt, an dem das Bild das Sichtfeld verlässt).

Seit collectionView.visibleCells gibt auch Zellen zurück, die sich unter den oberen/unteren Balken befinden, obwohl sie für den Benutzer nicht sichtbar sind, ist es dennoch eine gute Praxis, die Parallaxenwerte zu begrenzen (5).

Weitere Artikel wie diesen finden Sie in meinem Blog oder abonnieren Sie meinen monatlichen Newsletter. Ursprünglich erschienen bei https://rolandleth.com.

Similar Posts

Leave a Reply

Your email address will not be published.