LoginSignup
1
1

More than 3 years have passed since last update.

Transformを使用して簡単に視差効果を表現する

Posted at

今回はアプリの初回チュートリアルなどでスクロールをカッコよく見せる演出として使われるパララックス(視差効果)を表現する実装をご紹介します。
今回のものはサンプルなので見た目はイマイチですが、参考程度にどうぞ。

完成イメージ

白い正方形のViewには視差効果を与えておらず、1ページ目の青いLabel、2ページ目の黒いLabelに視差効果を与えています。
青のLabelと白いViewのスクロール量が少しずれていることがわかると思います。

実装概要

  • UIScrollViewでページングをする横スクロールを作成します
  • UIScrollView内にはUIStackViewを配置し、任意のページ分のコンテンツを追加してスクロールできるようにします。
  • 各ページのUIViewのtransform, alphaにアクセスして、スクロール量に応じて値を変化させます。

View階層

実装

今回はUI実装の簡略化のためにSnapKitというライブラリを使用させていただきます。

import UIKit
import SnapKit

final class ParallaxViewController: UIViewController {
    let scrollView: UIScrollView = {
        let scroll = UIScrollView()
        scroll.isPagingEnabled = true
        return scroll
    }()

    let stackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .horizontal
        stack.alignment = .fill
        stack.backgroundColor = .purple
        return stack
    }()

    let containers: [LabelContainer] = [
        LabelContainer(labelColor: .blue),
        LabelContainer(labelColor: .black)
    ]

    private var contentOffsetObservation: NSKeyValueObservation?

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .white

        configureScrollView()

        contentOffsetObservation = scrollView.observe(\.contentOffset) { [weak self] scrollView, _ in
            guard let me = self else { return }

            me.containers.enumerated().forEach { index, container in
                let distanceContainerToLabel = container.frame.origin.x - scrollView.contentOffset.x
                // スクロール量に応じてLabelの表示位置をずらす
                container.label.transform = .init(
                    translationX: distanceContainerToLabel * -0.3,
                    y: .zero
                )

                let limit = me.scrollView.bounds.width
                // スクロール量に応じてLabelの透明度を変化させる
                container.alpha = {
                    switch distanceContainerToLabel {
                    case -limit..<0, 0..<limit:
                        return 1 - abs(distanceContainerToLabel) / limit
                    default:
                        return .zero
                    }
                }()
            }
        }
    }

    private func configureScrollView() {
        view.addSubview(scrollView)
        scrollView.snp.makeConstraints {
            $0.top.left.right.equalToSuperview()
            $0.height.equalTo(300)
        }

        scrollView.addSubview(stackView)
        stackView.snp.makeConstraints {
            $0.edges.height.equalToSuperview()
        }

        containers.forEach { container in
            stackView.addArrangedSubview(container)
            container.snp.makeConstraints {
                $0.width.equalTo(view.snp.width)
            }
        }

        let box = UIView()
        box.backgroundColor = .white
        scrollView.addSubview(box)
        box.snp.makeConstraints {
            $0.width.height.equalTo(200)
            $0.center.equalToSuperview()
        }
    }
}

final class LabelContainer: UIView {
    let label: UILabel

    init(labelColor: UIColor) {
        label = makeLabel(color: labelColor)
        super.init(frame: .zero)

        addSubview(label)
        label.snp.makeConstraints { $0.edges.equalToSuperview() }
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

fileprivate func makeLabel(color: UIColor) -> UILabel {
    let label = UILabel()
    label.text = Array(repeating: "LABEL", count: 100).joined()
    label.textAlignment = .center
    label.numberOfLines = 0
    label.textColor = .white
    label.backgroundColor = color
    return label
}
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1